feedtack 1.2.0 → 1.3.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.
@@ -0,0 +1,199 @@
1
+ "use strict";(()=>{var P="2.0.0";function v(){return{width:window.innerWidth,height:window.innerHeight,scrollX:window.scrollX,scrollY:window.scrollY,devicePixelRatio:window.devicePixelRatio}}function w(){return{url:window.location.href,pathname:window.location.pathname,title:document.title}}function E(){return{userAgent:navigator.userAgent,platform:navigator.platform,touchEnabled:navigator.maxTouchPoints>0}}function C(n){let t=n.clientX+window.scrollX,e=n.clientY+window.scrollY,o=document.documentElement.scrollWidth,a=document.documentElement.scrollHeight;return{x:t,y:e,xPct:Number((t/o*100).toFixed(2)),yPct:Number((e/a*100).toFixed(2))}}var A="button,a,input,select,textarea,label";function S(n){return n.closest(A)??n}function f(n,t){return n.getAttribute(t)}function U(n){let t=1,e=n.previousElementSibling;for(;e;)t++,e=e.previousElementSibling;return t}function B(n){let t=n.tagName,e=1,o=n.previousElementSibling;for(;o;)o.tagName===t&&e++,o=o.previousElementSibling;return e}function R(n){let t=f(n,"id"),e=f(n,"data-testid")??f(n,"data-test-id"),o=f(n,"data-feedtack-component"),a=!!(t||e);return{tag:n.tagName.toLowerCase(),id:t,ariaLabel:f(n,"aria-label"),role:f(n,"role"),type:f(n,"type"),name:f(n,"name"),title:f(n,"title"),alt:f(n,"alt"),dataTestId:e,dataFeedtackComponent:o,nthChild:a?null:U(n),nthOfType:a?null:B(n),componentName:o??null}}function _(n){let t=[],e=n.parentElement;for(;e&&e!==document.body&&t.length<5;)t.push(R(e)),e=e.parentElement;return t}function $(n){let t=[],e=n;for(;e&&e!==document.body;){let o=e.getAttribute("id"),a=e.getAttribute("data-testid")??e.getAttribute("data-test-id"),r=e.getAttribute("data-feedtack-component");if(o){t.unshift(`#${o}`);break}else if(a){t.unshift(`[data-testid="${a}"]`);break}else if(r){t.unshift(`[data-feedtack-component="${r}"]`);break}else{let s=e.tagName.toLowerCase(),p=e.parentElement;if(p){let d=Array.from(p.children).filter(g=>g.tagName===e.tagName),m=d.indexOf(e)+1;t.unshift(d.length>1?`${s}:nth-of-type(${m})`:s)}else t.unshift(s)}e=e.parentElement}return t.join(" > ")}function D(n,t){if(n.getAttribute("data-testid")??n.getAttribute("data-test-id"))return null;let o=(()=>{let r=n.tagName.toLowerCase(),s=Array.from(n.classList).join(".");return s?`${r}.${s}`:r})(),a=t.map(r=>r.dataTestId?`[data-testid="${r.dataTestId}"]`:r.tag);return[o,...a].join(" > ")}function F(n){let t=S(n),e=t.getAttribute("id"),o=t.getAttribute("data-testid")??t.getAttribute("data-test-id"),a=t.getAttribute("data-feedtack-component"),r,s;e?(r=`#${e}`,s=!1):o?(r=`[data-testid="${o}"]`,s=!1):a?(r=`[data-feedtack-component="${a}"]`,s=!1):(r=$(t),s=!0);let p=_(t),d=t.getBoundingClientRect();return{selector:r,best_effort:s,dataTestId:o,elementPath:D(t,p),tagName:t.tagName,ancestors:p,boundingRect:{x:d.x+window.scrollX,y:d.y+window.scrollY,width:d.width,height:d.height}}}var y={id:"anon",name:"Anonymous",role:"reviewer"};var H="1.2.0";function z(n){let t=new URL(n);if(t.protocol!=="https:")throw new Error(`[feedtack] Webhook URL must use https: protocol, got ${t.protocol}`);return t.href}function M(n){let t={version:H,user:y};return n&&(n.webhookUrl&&(t.webhookUrl=z(n.webhookUrl)),n.user&&(t.user={id:n.user.id??y.id,name:n.user.name??y.name,role:n.user.role??y.role,...n.user.avatarUrl?{avatarUrl:n.user.avatarUrl}:{},...n.user.email?{email:n.user.email}:{}})),t}async function I(n,t){let e=JSON.stringify(n,null,2);if(t.webhookUrl){let o=new Blob([e],{type:"application/json"});if(!navigator.sendBeacon(t.webhookUrl,o))throw new Error("[feedtack] sendBeacon failed \u2014 payload may be too large");return"webhook"}return await navigator.clipboard.writeText(e),"clipboard"}var T=`
2
+ :host {
3
+ all: initial;
4
+ direction: ltr;
5
+ font-family: system-ui, -apple-system, sans-serif;
6
+ line-height: 1.5;
7
+ color: #111827;
8
+ }
9
+
10
+ * {
11
+ box-sizing: border-box;
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ .ft-fab {
17
+ position: fixed;
18
+ bottom: 24px;
19
+ right: 24px;
20
+ z-index: 2147483640;
21
+ background: #111827;
22
+ color: #fff;
23
+ border: none;
24
+ border-radius: 8px;
25
+ padding: 10px 16px;
26
+ font-size: 13px;
27
+ font-weight: 500;
28
+ cursor: pointer;
29
+ box-shadow: 0 2px 8px rgba(0,0,0,0.25);
30
+ min-width: 44px;
31
+ min-height: 44px;
32
+ display: flex;
33
+ align-items: center;
34
+ gap: 6px;
35
+ transition: background 0.15s;
36
+ }
37
+
38
+ .ft-fab:hover { opacity: 0.85; }
39
+ .ft-fab.active { background: #2563eb; }
40
+
41
+ .ft-panel {
42
+ position: fixed;
43
+ bottom: 80px;
44
+ right: 24px;
45
+ z-index: 2147483641;
46
+ width: 320px;
47
+ max-height: 80vh;
48
+ background: #fff;
49
+ border: 1px solid #e5e7eb;
50
+ border-radius: 12px;
51
+ box-shadow: 0 8px 32px rgba(0,0,0,0.18);
52
+ display: none;
53
+ flex-direction: column;
54
+ overflow: hidden;
55
+ }
56
+
57
+ .ft-panel.open { display: flex; }
58
+
59
+ .ft-panel-header {
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: space-between;
63
+ padding: 14px 16px;
64
+ border-bottom: 1px solid #e5e7eb;
65
+ }
66
+
67
+ .ft-panel-title {
68
+ font-size: 15px;
69
+ font-weight: 600;
70
+ }
71
+
72
+ .ft-panel-close {
73
+ background: none;
74
+ border: none;
75
+ font-size: 20px;
76
+ cursor: pointer;
77
+ color: #6b7280;
78
+ padding: 0 4px;
79
+ min-width: 44px;
80
+ min-height: 44px;
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ }
85
+
86
+ .ft-panel-body {
87
+ padding: 16px;
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: 12px;
91
+ overflow-y: auto;
92
+ }
93
+
94
+ .ft-textarea {
95
+ width: 100%;
96
+ border: 1.5px solid #e5e7eb;
97
+ border-radius: 8px;
98
+ padding: 8px;
99
+ font-size: 13px;
100
+ font-family: inherit;
101
+ resize: vertical;
102
+ min-height: 80px;
103
+ outline: none;
104
+ background: #f9fafb;
105
+ color: #111827;
106
+ }
107
+
108
+ .ft-textarea:focus { border-color: #2563eb; }
109
+ .ft-textarea.error { border-color: #ef4444; }
110
+
111
+ .ft-sentiment {
112
+ display: flex;
113
+ gap: 8px;
114
+ }
115
+
116
+ .ft-sentiment button {
117
+ flex: 1;
118
+ padding: 6px 10px;
119
+ border: 1.5px solid #e5e7eb;
120
+ border-radius: 8px;
121
+ background: #fff;
122
+ color: #111827;
123
+ font-size: 12px;
124
+ cursor: pointer;
125
+ min-height: 44px;
126
+ transition: all 0.1s;
127
+ }
128
+
129
+ .ft-sentiment button.selected {
130
+ border-color: #2563eb;
131
+ background: #f9fafb;
132
+ color: #2563eb;
133
+ }
134
+
135
+ .ft-actions {
136
+ display: flex;
137
+ gap: 8px;
138
+ justify-content: flex-end;
139
+ }
140
+
141
+ .ft-btn-cancel {
142
+ padding: 8px 14px;
143
+ border: 1.5px solid #e5e7eb;
144
+ border-radius: 8px;
145
+ background: #fff;
146
+ color: #111827;
147
+ font-size: 13px;
148
+ cursor: pointer;
149
+ min-height: 44px;
150
+ }
151
+
152
+ .ft-btn-submit {
153
+ padding: 8px 14px;
154
+ border: none;
155
+ border-radius: 8px;
156
+ background: #2563eb;
157
+ color: #fff;
158
+ font-size: 13px;
159
+ font-weight: 500;
160
+ cursor: pointer;
161
+ min-height: 44px;
162
+ }
163
+
164
+ .ft-btn-submit:disabled {
165
+ opacity: 0.5;
166
+ cursor: not-allowed;
167
+ }
168
+
169
+ .ft-pin-count {
170
+ font-size: 12px;
171
+ color: #6b7280;
172
+ padding: 4px 0;
173
+ }
174
+
175
+ .ft-status {
176
+ font-size: 12px;
177
+ color: #22c55e;
178
+ padding: 4px 0;
179
+ text-align: center;
180
+ }
181
+
182
+ .ft-error-msg {
183
+ font-size: 12px;
184
+ color: #ef4444;
185
+ }
186
+
187
+ @media (max-width: 480px) {
188
+ .ft-panel {
189
+ right: 0;
190
+ bottom: 72px;
191
+ width: 100vw;
192
+ max-height: 80vh;
193
+ border-radius: 12px 12px 0 0;
194
+ border-left: none;
195
+ border-right: none;
196
+ border-bottom: none;
197
+ }
198
+ }
199
+ `;function N(){let n=document.createElement("div");n.id="feedtack-inject",document.body.appendChild(n);let t=n.attachShadow({mode:"closed"}),e=document.createElement("style");e.textContent=T,t.appendChild(e);let o=document.createElement("button");o.className="ft-fab",o.textContent="Feedback",o.setAttribute("aria-label","Toggle feedtack feedback panel"),t.appendChild(o);let a=document.createElement("div");a.className="ft-panel",a.setAttribute("role","dialog"),a.setAttribute("aria-label","Feedtack feedback panel");let r=document.createElement("div");r.className="ft-panel-header";let s=document.createElement("span");s.className="ft-panel-title",s.textContent="Feedback";let p=document.createElement("button");p.className="ft-panel-close",p.textContent="\xD7",p.setAttribute("aria-label","Close panel"),r.appendChild(s),r.appendChild(p),a.appendChild(r);let d=document.createElement("div");d.className="ft-panel-body";let m=document.createElement("div");m.className="ft-pin-count",m.textContent="Click on elements to place pins, then submit.",d.appendChild(m);let g=document.createElement("textarea");g.className="ft-textarea",g.placeholder="Describe your feedback...",g.setAttribute("aria-label","Feedback comment"),d.appendChild(g);let b=document.createElement("div");b.className="ft-error-msg",b.style.display="none",b.textContent="Please enter a comment.",d.appendChild(b);let x=document.createElement("div");x.className="ft-sentiment";let i=document.createElement("button");i.textContent="Good",i.setAttribute("aria-label","Mark as good");let c=document.createElement("button");c.textContent="Bad",c.setAttribute("aria-label","Mark as bad"),x.appendChild(i),x.appendChild(c),d.appendChild(x);let l=document.createElement("div");l.className="ft-actions";let u=document.createElement("button");u.className="ft-btn-cancel",u.textContent="Cancel";let h=document.createElement("button");h.className="ft-btn-submit",h.textContent="Submit",l.appendChild(u),l.appendChild(h),d.appendChild(l);let k=document.createElement("div");return k.className="ft-status",k.style.display="none",d.appendChild(k),a.appendChild(d),t.appendChild(a),{shadow:t,fab:o,panel:a,textarea:g,sentimentGood:i,sentimentBad:c,submitBtn:h,cancelBtn:u,pinCount:m,status:k,errorMsg:b}}var j="#2563eb";function O(n,t,e){let o=document.createElement("div");o.style.cssText=["position:absolute",`left:${n}px`,`top:${t}px`,"z-index:2147483641","width:24px","height:24px","border-radius:50% 50% 50% 0","transform:translate(-50%,-100%) rotate(-45deg)",`background:${j}`,"border:2px solid rgba(255,255,255,0.8)","box-shadow:0 2px 6px rgba(0,0,0,0.3)","pointer-events:none"].join(";");let a=document.createElement("span");return a.style.cssText=["position:absolute","inset:0","display:flex","align-items:center","justify-content:center","transform:rotate(45deg)","font-size:12px","font-weight:700","color:#fff","line-height:1"].join(";"),a.textContent=String(e),o.appendChild(a),document.body.appendChild(o),o}function V(n){for(let t of n)t.el.remove();n.length=0}function L(n,t){n.pinCount.textContent=t===0?"Click on elements to place pins, then submit.":`${t} pin${t>1?"s":""} placed. Click more or submit.`}(function(){if(window.__feedtack_injected){console.warn("[feedtack] Already injected \u2014 skipping.");return}window.__feedtack_injected=!0;let t=M(window.__feedtack),e=N(),o=[],a=!1,r=null,s=!1;function p(i){a=i,e.fab.classList.toggle("active",i),i?document.documentElement.style.cursor="crosshair":document.documentElement.style.cursor=""}function d(){s=!0,e.panel.classList.add("open"),p(!0)}function m(){s=!1,e.panel.classList.remove("open"),p(!1),V(o),e.textarea.value="",r=null,e.sentimentGood.classList.remove("selected"),e.sentimentBad.classList.remove("selected"),e.errorMsg.style.display="none",e.status.style.display="none",L(e,0)}e.fab.addEventListener("click",()=>{s?m():d()}),e.panel.querySelector(".ft-panel-close")?.addEventListener("click",m),e.sentimentGood.addEventListener("click",()=>{r=r==="good"?null:"good",e.sentimentGood.classList.toggle("selected",r==="good"),e.sentimentBad.classList.remove("selected")}),e.sentimentBad.addEventListener("click",()=>{r=r==="bad"?null:"bad",e.sentimentBad.classList.toggle("selected",r==="bad"),e.sentimentGood.classList.remove("selected")}),e.cancelBtn.addEventListener("click",m);function g(i){if(!a||!s)return;let c=i.target;c.closest("#feedtack-inject")||(i.preventDefault(),i.stopPropagation(),x(i.clientX,i.clientY,c))}function b(i){if(!a||!s)return;let c=i.changedTouches[0];if(!c)return;let l=document.elementFromPoint(c.clientX,c.clientY);!l||l.closest("#feedtack-inject")||(i.preventDefault(),x(c.clientX,c.clientY,l))}function x(i,c,l){let u=C({clientX:i,clientY:c}),h={color:j,...u,target:F(l)},k=O(u.x,u.y,o.length+1);o.push({el:k,pin:h}),L(e,o.length)}document.addEventListener("click",g,!0),document.addEventListener("touchend",b,!0),window.addEventListener("keydown",i=>{i.key==="Escape"&&s&&m()}),e.submitBtn.addEventListener("click",async()=>{let i=e.textarea.value.trim();if(!i){e.errorMsg.style.display="block",e.textarea.classList.add("error");return}e.errorMsg.style.display="none",e.textarea.classList.remove("error"),e.submitBtn.disabled=!0;let c={schemaVersion:P,id:crypto.randomUUID(),timestamp:new Date().toISOString(),scope:o.length>0?"element":"page",submittedBy:t.user,comment:i,sentiment:r,pins:o.map((l,u)=>({...l.pin,index:u+1})),page:w(),viewport:v(),device:E()};try{let l=await I(c,t);e.status.textContent=l==="clipboard"?"Copied to clipboard!":"Sent to webhook!",e.status.style.display="block",setTimeout(m,1500)}catch(l){e.errorMsg.textContent=l.message,e.errorMsg.style.display="block"}finally{e.submitBtn.disabled=!1}}),e.textarea.addEventListener("input",()=>{e.errorMsg.style.display="none",e.textarea.classList.remove("error")}),console.log(`[feedtack] Injectable snippet loaded (v${t.version}, ${t.webhookUrl?"webhook":"clipboard"} mode)`)})();})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feedtack",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Click anywhere. Drop a pin. Get a payload a developer can act on.",
5
5
  "type": "module",
6
6
  "license": "MIT",