@loupeink/web-sdk 1.0.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/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/index.cjs +499 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.global.js +437 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +457 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
// src/styles.ts
|
|
2
|
+
var WIDGET_CSS = `
|
|
3
|
+
.loupe-btn {
|
|
4
|
+
position: fixed;
|
|
5
|
+
z-index: 2147483647;
|
|
6
|
+
background-color: #10b981;
|
|
7
|
+
color: #ffffff;
|
|
8
|
+
border: none;
|
|
9
|
+
border-radius: 9999px;
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
padding: 8px 16px;
|
|
12
|
+
font-size: 14px;
|
|
13
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
14
|
+
font-weight: 500;
|
|
15
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.loupe-btn:hover {
|
|
19
|
+
opacity: 0.9;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.bottom-right {
|
|
23
|
+
bottom: 24px;
|
|
24
|
+
right: 24px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.bottom-left {
|
|
28
|
+
bottom: 24px;
|
|
29
|
+
left: 24px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.top-right {
|
|
33
|
+
top: 24px;
|
|
34
|
+
right: 24px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.top-left {
|
|
38
|
+
top: 24px;
|
|
39
|
+
left: 24px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.loupe-modal {
|
|
43
|
+
display: none;
|
|
44
|
+
position: fixed;
|
|
45
|
+
z-index: 2147483646;
|
|
46
|
+
bottom: 80px;
|
|
47
|
+
right: 24px;
|
|
48
|
+
background: #18181b;
|
|
49
|
+
border: 1px solid #3f3f46;
|
|
50
|
+
border-radius: 12px;
|
|
51
|
+
padding: 24px;
|
|
52
|
+
min-width: 320px;
|
|
53
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
|
|
54
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
55
|
+
color: #f4f4f5;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.loupe-modal.modal--visible {
|
|
59
|
+
display: block;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.loupe-modal textarea {
|
|
63
|
+
width: 100%;
|
|
64
|
+
box-sizing: border-box;
|
|
65
|
+
background: #27272a;
|
|
66
|
+
border: 1px solid #3f3f46;
|
|
67
|
+
border-radius: 6px;
|
|
68
|
+
color: #f4f4f5;
|
|
69
|
+
font-size: 14px;
|
|
70
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
71
|
+
padding: 8px;
|
|
72
|
+
margin-bottom: 12px;
|
|
73
|
+
resize: vertical;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.loupe-modal select {
|
|
77
|
+
width: 100%;
|
|
78
|
+
box-sizing: border-box;
|
|
79
|
+
background: #27272a;
|
|
80
|
+
border: 1px solid #3f3f46;
|
|
81
|
+
border-radius: 6px;
|
|
82
|
+
color: #f4f4f5;
|
|
83
|
+
font-size: 14px;
|
|
84
|
+
padding: 8px;
|
|
85
|
+
margin-bottom: 16px;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.loupe-modal-actions {
|
|
90
|
+
display: flex;
|
|
91
|
+
gap: 8px;
|
|
92
|
+
justify-content: flex-end;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.loupe-modal-submit {
|
|
96
|
+
background: #10b981;
|
|
97
|
+
color: #fff;
|
|
98
|
+
border: none;
|
|
99
|
+
border-radius: 6px;
|
|
100
|
+
padding: 8px 16px;
|
|
101
|
+
font-size: 14px;
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.loupe-modal-cancel {
|
|
106
|
+
background: transparent;
|
|
107
|
+
color: #a1a1aa;
|
|
108
|
+
border: 1px solid #3f3f46;
|
|
109
|
+
border-radius: 6px;
|
|
110
|
+
padding: 8px 16px;
|
|
111
|
+
font-size: 14px;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
// src/screenshot.ts
|
|
117
|
+
import html2canvas from "html2canvas-pro";
|
|
118
|
+
async function captureScreenshot(widgetHostEl) {
|
|
119
|
+
widgetHostEl.style.display = "none";
|
|
120
|
+
try {
|
|
121
|
+
const canvas = await html2canvas(document.documentElement, {
|
|
122
|
+
useCORS: true,
|
|
123
|
+
allowTaint: false,
|
|
124
|
+
logging: false,
|
|
125
|
+
x: window.scrollX,
|
|
126
|
+
y: window.scrollY,
|
|
127
|
+
width: window.innerWidth,
|
|
128
|
+
height: window.innerHeight,
|
|
129
|
+
windowWidth: window.innerWidth,
|
|
130
|
+
windowHeight: window.innerHeight
|
|
131
|
+
});
|
|
132
|
+
return canvas.toDataURL("image/jpeg", 0.8);
|
|
133
|
+
} finally {
|
|
134
|
+
widgetHostEl.style.display = "";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/context.ts
|
|
139
|
+
function capturePageContext() {
|
|
140
|
+
return {
|
|
141
|
+
url: window.location.href,
|
|
142
|
+
title: document.title,
|
|
143
|
+
viewportWidth: window.innerWidth,
|
|
144
|
+
viewportHeight: window.innerHeight,
|
|
145
|
+
userAgent: navigator.userAgent,
|
|
146
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/submit.ts
|
|
151
|
+
async function submitFeedback(opts) {
|
|
152
|
+
const response = await fetch(opts.edgeFunctionUrl, {
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers: {
|
|
155
|
+
"Content-Type": "application/json",
|
|
156
|
+
"X-API-Key": opts.apiKey
|
|
157
|
+
},
|
|
158
|
+
body: JSON.stringify({
|
|
159
|
+
comment: opts.comment,
|
|
160
|
+
severity: opts.severity,
|
|
161
|
+
screenshot: opts.screenshotDataUrl,
|
|
162
|
+
context: opts.context
|
|
163
|
+
})
|
|
164
|
+
});
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
throw new Error("Submit failed: " + response.status);
|
|
167
|
+
}
|
|
168
|
+
return response.json();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/annotation-step.tsx
|
|
172
|
+
import React from "react";
|
|
173
|
+
import { createRoot } from "react-dom/client";
|
|
174
|
+
import { KonvaCanvas } from "@loupeink/ui";
|
|
175
|
+
var ANNOTATION_CSS = `
|
|
176
|
+
/* \u2500\u2500 Design tokens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
177
|
+
.loupe-annotation-wrap {
|
|
178
|
+
--color-surface-0: #09090b;
|
|
179
|
+
--color-surface-1: #121215;
|
|
180
|
+
--color-surface-2: #1a1a1f;
|
|
181
|
+
--color-surface-3: #222228;
|
|
182
|
+
--color-text-primary: #fafafa;
|
|
183
|
+
--color-text-secondary: #a1a1aa;
|
|
184
|
+
--color-border-default: #27272a;
|
|
185
|
+
--color-accent: #34d399;
|
|
186
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
187
|
+
font-size: 14px;
|
|
188
|
+
color: var(--color-text-primary);
|
|
189
|
+
box-sizing: border-box;
|
|
190
|
+
}
|
|
191
|
+
.loupe-annotation-wrap *, .loupe-annotation-wrap *::before, .loupe-annotation-wrap *::after {
|
|
192
|
+
box-sizing: border-box;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* \u2500\u2500 Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
196
|
+
.flex { display: flex; }
|
|
197
|
+
.flex-col { flex-direction: column; }
|
|
198
|
+
.flex-wrap { flex-wrap: wrap; }
|
|
199
|
+
.flex-1 { flex: 1 1 0%; }
|
|
200
|
+
.flex-shrink-0 { flex-shrink: 0; }
|
|
201
|
+
.items-center { align-items: center; }
|
|
202
|
+
.justify-between { justify-content: space-between; }
|
|
203
|
+
.gap-1 { gap: 4px; }
|
|
204
|
+
.gap-0\\.5 { gap: 2px; }
|
|
205
|
+
.space-y-1 > * + * { margin-top: 4px; }
|
|
206
|
+
.min-w-0 { min-width: 0; }
|
|
207
|
+
|
|
208
|
+
/* \u2500\u2500 Sizing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
209
|
+
.h-full { height: 100%; }
|
|
210
|
+
.h-4 { height: 16px; }
|
|
211
|
+
.h-6 { height: 24px; }
|
|
212
|
+
.h-8 { height: 32px; }
|
|
213
|
+
.w-4 { width: 16px; }
|
|
214
|
+
.w-6 { width: 24px; }
|
|
215
|
+
.w-8 { width: 32px; }
|
|
216
|
+
.w-px { width: 1px; }
|
|
217
|
+
.w-72 { width: 288px; }
|
|
218
|
+
|
|
219
|
+
/* \u2500\u2500 Spacing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
220
|
+
.p-0 { padding: 0; }
|
|
221
|
+
.p-3 { padding: 12px; }
|
|
222
|
+
.p-4 { padding: 16px; }
|
|
223
|
+
.px-1\\.5 { padding-left: 6px; padding-right: 6px; }
|
|
224
|
+
.px-2 { padding-left: 8px; padding-right: 8px; }
|
|
225
|
+
.px-3 { padding-left: 12px; padding-right: 12px; }
|
|
226
|
+
.px-4 { padding-left: 16px; padding-right: 16px; }
|
|
227
|
+
.py-0\\.5 { padding-top: 2px; padding-bottom: 2px; }
|
|
228
|
+
.py-2 { padding-top: 8px; padding-bottom: 8px; }
|
|
229
|
+
.m-0 { margin: 0; }
|
|
230
|
+
.mx-1 { margin-left: 4px; margin-right: 4px; }
|
|
231
|
+
.mr-1 { margin-right: 4px; }
|
|
232
|
+
.mt-1 { margin-top: 4px; }
|
|
233
|
+
.mb-2 { margin-bottom: 8px; }
|
|
234
|
+
|
|
235
|
+
/* \u2500\u2500 Position \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
236
|
+
.relative { position: relative; }
|
|
237
|
+
.absolute { position: absolute; }
|
|
238
|
+
.right-0 { right: 0; }
|
|
239
|
+
.top-full { top: 100%; }
|
|
240
|
+
.z-50 { z-index: 50; }
|
|
241
|
+
.overflow-hidden { overflow: hidden; }
|
|
242
|
+
|
|
243
|
+
/* \u2500\u2500 Colours \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
244
|
+
.bg-surface-1 { background-color: var(--color-surface-1); }
|
|
245
|
+
.bg-surface-2 { background-color: var(--color-surface-2); }
|
|
246
|
+
.bg-surface-3 { background-color: var(--color-surface-3); }
|
|
247
|
+
.bg-transparent { background-color: transparent; }
|
|
248
|
+
.bg-accent { background-color: var(--color-accent); }
|
|
249
|
+
.text-text-primary { color: var(--color-text-primary); }
|
|
250
|
+
.text-text-secondary { color: var(--color-text-secondary); }
|
|
251
|
+
.text-white { color: #fff; }
|
|
252
|
+
|
|
253
|
+
/* \u2500\u2500 Borders \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
254
|
+
.border { border-width: 1px; border-style: solid; }
|
|
255
|
+
.border-b { border-bottom-width: 1px; border-bottom-style: solid; }
|
|
256
|
+
.border-none { border: none; }
|
|
257
|
+
.border-border-default { border-color: var(--color-border-default); }
|
|
258
|
+
.border-accent { border-color: var(--color-accent); }
|
|
259
|
+
.rounded { border-radius: 4px; }
|
|
260
|
+
.rounded-full { border-radius: 9999px; }
|
|
261
|
+
.rounded-lg { border-radius: 8px; }
|
|
262
|
+
.shadow-lg { box-shadow: 0 10px 15px -3px rgba(0,0,0,0.4), 0 4px 6px -2px rgba(0,0,0,0.2); }
|
|
263
|
+
|
|
264
|
+
/* \u2500\u2500 Typography \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
265
|
+
.text-xs { font-size: 12px; line-height: 16px; }
|
|
266
|
+
.text-sm { font-size: 14px; line-height: 20px; }
|
|
267
|
+
.font-mono { font-family: ui-monospace, monospace; }
|
|
268
|
+
.font-semibold { font-weight: 600; }
|
|
269
|
+
.font-bold { font-weight: 700; }
|
|
270
|
+
.uppercase { text-transform: uppercase; }
|
|
271
|
+
.tracking-wide { letter-spacing: 0.025em; }
|
|
272
|
+
.outline-none { outline: none; }
|
|
273
|
+
.resize-none { resize: none; }
|
|
274
|
+
.transition-colors { transition: color 150ms, background-color 150ms, border-color 150ms; }
|
|
275
|
+
|
|
276
|
+
/* \u2500\u2500 Hover states \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
277
|
+
.hover\\:text-text-primary:hover { color: var(--color-text-primary); }
|
|
278
|
+
.hover\\:bg-surface-2:hover { background-color: var(--color-surface-2); }
|
|
279
|
+
|
|
280
|
+
/* \u2500\u2500 Ring (active color swatch outline) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
281
|
+
.ring-2.ring-white.ring-offset-1 {
|
|
282
|
+
box-shadow: 0 0 0 1px var(--color-surface-1), 0 0 0 3px #fff;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* \u2500\u2500 Button base reset (so host-page styles don't bleed) \u2500\u2500\u2500 */
|
|
286
|
+
.loupe-annotation-wrap button {
|
|
287
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
288
|
+
cursor: pointer; border: 1px solid var(--color-border-default);
|
|
289
|
+
background: var(--color-surface-2); color: var(--color-text-primary);
|
|
290
|
+
font-family: inherit; font-size: 14px; line-height: 1;
|
|
291
|
+
}
|
|
292
|
+
.loupe-annotation-wrap button:hover {
|
|
293
|
+
background: var(--color-surface-3);
|
|
294
|
+
}
|
|
295
|
+
`;
|
|
296
|
+
function mountAnnotationStep(shadow, screenshotUrl, callbacks) {
|
|
297
|
+
const styleEl = document.createElement("style");
|
|
298
|
+
styleEl.textContent = ANNOTATION_CSS;
|
|
299
|
+
shadow.appendChild(styleEl);
|
|
300
|
+
const container = document.createElement("div");
|
|
301
|
+
container.className = "loupe-annotation-wrap";
|
|
302
|
+
container.style.cssText = "position:fixed;inset:0;z-index:2147483647;background:rgba(0,0,0,0.85);display:flex;flex-direction:column;";
|
|
303
|
+
shadow.appendChild(container);
|
|
304
|
+
const root = createRoot(container);
|
|
305
|
+
function cleanup() {
|
|
306
|
+
root.unmount();
|
|
307
|
+
container.remove();
|
|
308
|
+
styleEl.remove();
|
|
309
|
+
}
|
|
310
|
+
root.render(
|
|
311
|
+
React.createElement(KonvaCanvas, {
|
|
312
|
+
screenshotUrl,
|
|
313
|
+
existingAnnotations: [],
|
|
314
|
+
onSave: (annotations, dataUrl) => {
|
|
315
|
+
cleanup();
|
|
316
|
+
callbacks.onSave(annotations, dataUrl);
|
|
317
|
+
},
|
|
318
|
+
onCancel: () => {
|
|
319
|
+
cleanup();
|
|
320
|
+
callbacks.onCancel();
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/widget.ts
|
|
327
|
+
var LoupeWidget = class {
|
|
328
|
+
constructor(config) {
|
|
329
|
+
this.hostEl = null;
|
|
330
|
+
this.shadow = null;
|
|
331
|
+
this.config = {
|
|
332
|
+
apiKey: config.apiKey,
|
|
333
|
+
edgeFunctionUrl: config.edgeFunctionUrl ?? "https://etdekhjnkevmrkgqixow.supabase.co/functions/v1/submit-sdk-feedback",
|
|
334
|
+
position: config.position ?? "bottom-right",
|
|
335
|
+
color: config.color ?? "#10b981",
|
|
336
|
+
buttonLabel: config.buttonLabel ?? "Feedback"
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
mount(container) {
|
|
340
|
+
this.hostEl = document.createElement("div");
|
|
341
|
+
this.hostEl.id = "loupe-widget-host";
|
|
342
|
+
this.shadow = this.hostEl.attachShadow({ mode: "open" });
|
|
343
|
+
const style = document.createElement("style");
|
|
344
|
+
style.textContent = WIDGET_CSS;
|
|
345
|
+
this.shadow.appendChild(style);
|
|
346
|
+
const button = document.createElement("button");
|
|
347
|
+
button.className = `loupe-btn ${this.config.position}`;
|
|
348
|
+
button.textContent = this.config.buttonLabel;
|
|
349
|
+
if (this.config.color) {
|
|
350
|
+
button.style.backgroundColor = this.config.color;
|
|
351
|
+
}
|
|
352
|
+
const modal = document.createElement("div");
|
|
353
|
+
modal.className = "loupe-modal";
|
|
354
|
+
modal.setAttribute("data-loupe-modal", "");
|
|
355
|
+
modal.setAttribute("aria-modal", "true");
|
|
356
|
+
modal.setAttribute("role", "dialog");
|
|
357
|
+
const textarea = document.createElement("textarea");
|
|
358
|
+
textarea.placeholder = "Describe the issue...";
|
|
359
|
+
textarea.rows = 4;
|
|
360
|
+
const select = document.createElement("select");
|
|
361
|
+
const severities = ["critical", "major", "minor", "suggestion"];
|
|
362
|
+
for (const s of severities) {
|
|
363
|
+
const opt = document.createElement("option");
|
|
364
|
+
opt.value = s;
|
|
365
|
+
opt.textContent = s.charAt(0).toUpperCase() + s.slice(1);
|
|
366
|
+
select.appendChild(opt);
|
|
367
|
+
}
|
|
368
|
+
const actions = document.createElement("div");
|
|
369
|
+
actions.className = "loupe-modal-actions";
|
|
370
|
+
const submitBtn = document.createElement("button");
|
|
371
|
+
submitBtn.className = "loupe-modal-submit";
|
|
372
|
+
submitBtn.textContent = "Send Feedback";
|
|
373
|
+
const cancelBtn = document.createElement("button");
|
|
374
|
+
cancelBtn.className = "loupe-modal-cancel";
|
|
375
|
+
cancelBtn.textContent = "Cancel";
|
|
376
|
+
actions.appendChild(cancelBtn);
|
|
377
|
+
actions.appendChild(submitBtn);
|
|
378
|
+
modal.appendChild(textarea);
|
|
379
|
+
modal.appendChild(select);
|
|
380
|
+
modal.appendChild(actions);
|
|
381
|
+
this.shadow.appendChild(button);
|
|
382
|
+
this.shadow.appendChild(modal);
|
|
383
|
+
container.appendChild(this.hostEl);
|
|
384
|
+
button.addEventListener("click", () => {
|
|
385
|
+
modal.classList.toggle("modal--visible");
|
|
386
|
+
});
|
|
387
|
+
cancelBtn.addEventListener("click", () => {
|
|
388
|
+
modal.classList.remove("modal--visible");
|
|
389
|
+
});
|
|
390
|
+
submitBtn.addEventListener("click", async () => {
|
|
391
|
+
const comment = textarea.value.trim();
|
|
392
|
+
const severity = select.value;
|
|
393
|
+
const originalLabel = submitBtn.textContent;
|
|
394
|
+
submitBtn.textContent = "Capturing...";
|
|
395
|
+
submitBtn.setAttribute("disabled", "");
|
|
396
|
+
try {
|
|
397
|
+
const rawDataUrl = await captureScreenshot(this.hostEl).catch(() => null);
|
|
398
|
+
modal.classList.remove("modal--visible");
|
|
399
|
+
mountAnnotationStep(this.shadow, rawDataUrl ?? "", {
|
|
400
|
+
onSave: async (_annotations, annotatedDataUrl) => {
|
|
401
|
+
const context = capturePageContext();
|
|
402
|
+
try {
|
|
403
|
+
await submitFeedback({
|
|
404
|
+
apiKey: this.config.apiKey,
|
|
405
|
+
edgeFunctionUrl: this.config.edgeFunctionUrl,
|
|
406
|
+
comment,
|
|
407
|
+
severity,
|
|
408
|
+
screenshotDataUrl: annotatedDataUrl,
|
|
409
|
+
context
|
|
410
|
+
});
|
|
411
|
+
textarea.value = "";
|
|
412
|
+
} catch {
|
|
413
|
+
alert("Failed to send feedback. Please try again.");
|
|
414
|
+
} finally {
|
|
415
|
+
submitBtn.textContent = originalLabel;
|
|
416
|
+
submitBtn.removeAttribute("disabled");
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
onCancel: () => {
|
|
420
|
+
modal.classList.add("modal--visible");
|
|
421
|
+
submitBtn.textContent = originalLabel;
|
|
422
|
+
submitBtn.removeAttribute("disabled");
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
} catch {
|
|
426
|
+
alert("Failed to capture screenshot. Please try again.");
|
|
427
|
+
submitBtn.textContent = originalLabel;
|
|
428
|
+
submitBtn.removeAttribute("disabled");
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
destroy() {
|
|
433
|
+
this.hostEl?.remove();
|
|
434
|
+
this.hostEl = null;
|
|
435
|
+
this.shadow = null;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// src/index.ts
|
|
440
|
+
var currentWidget = null;
|
|
441
|
+
function init(config) {
|
|
442
|
+
currentWidget = new LoupeWidget(config);
|
|
443
|
+
currentWidget.mount(document.body);
|
|
444
|
+
}
|
|
445
|
+
function destroy() {
|
|
446
|
+
currentWidget?.destroy();
|
|
447
|
+
currentWidget = null;
|
|
448
|
+
}
|
|
449
|
+
export {
|
|
450
|
+
LoupeWidget,
|
|
451
|
+
capturePageContext,
|
|
452
|
+
captureScreenshot,
|
|
453
|
+
destroy,
|
|
454
|
+
init,
|
|
455
|
+
submitFeedback
|
|
456
|
+
};
|
|
457
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/styles.ts","../src/screenshot.ts","../src/context.ts","../src/submit.ts","../src/annotation-step.tsx","../src/widget.ts","../src/index.ts"],"sourcesContent":["export const WIDGET_CSS = `\n .loupe-btn {\n position: fixed;\n z-index: 2147483647;\n background-color: #10b981;\n color: #ffffff;\n border: none;\n border-radius: 9999px;\n cursor: pointer;\n padding: 8px 16px;\n font-size: 14px;\n font-family: system-ui, -apple-system, sans-serif;\n font-weight: 500;\n box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n }\n\n .loupe-btn:hover {\n opacity: 0.9;\n }\n\n .bottom-right {\n bottom: 24px;\n right: 24px;\n }\n\n .bottom-left {\n bottom: 24px;\n left: 24px;\n }\n\n .top-right {\n top: 24px;\n right: 24px;\n }\n\n .top-left {\n top: 24px;\n left: 24px;\n }\n\n .loupe-modal {\n display: none;\n position: fixed;\n z-index: 2147483646;\n bottom: 80px;\n right: 24px;\n background: #18181b;\n border: 1px solid #3f3f46;\n border-radius: 12px;\n padding: 24px;\n min-width: 320px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.4);\n font-family: system-ui, -apple-system, sans-serif;\n color: #f4f4f5;\n }\n\n .loupe-modal.modal--visible {\n display: block;\n }\n\n .loupe-modal textarea {\n width: 100%;\n box-sizing: border-box;\n background: #27272a;\n border: 1px solid #3f3f46;\n border-radius: 6px;\n color: #f4f4f5;\n font-size: 14px;\n font-family: system-ui, -apple-system, sans-serif;\n padding: 8px;\n margin-bottom: 12px;\n resize: vertical;\n }\n\n .loupe-modal select {\n width: 100%;\n box-sizing: border-box;\n background: #27272a;\n border: 1px solid #3f3f46;\n border-radius: 6px;\n color: #f4f4f5;\n font-size: 14px;\n padding: 8px;\n margin-bottom: 16px;\n cursor: pointer;\n }\n\n .loupe-modal-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n }\n\n .loupe-modal-submit {\n background: #10b981;\n color: #fff;\n border: none;\n border-radius: 6px;\n padding: 8px 16px;\n font-size: 14px;\n cursor: pointer;\n }\n\n .loupe-modal-cancel {\n background: transparent;\n color: #a1a1aa;\n border: 1px solid #3f3f46;\n border-radius: 6px;\n padding: 8px 16px;\n font-size: 14px;\n cursor: pointer;\n }\n`;\n","import html2canvas from 'html2canvas-pro';\n\nexport async function captureScreenshot(widgetHostEl: HTMLElement): Promise<string> {\n widgetHostEl.style.display = 'none';\n try {\n const canvas = await html2canvas(document.documentElement, {\n useCORS: true,\n allowTaint: false,\n logging: false,\n x: window.scrollX,\n y: window.scrollY,\n width: window.innerWidth,\n height: window.innerHeight,\n windowWidth: window.innerWidth,\n windowHeight: window.innerHeight,\n });\n return canvas.toDataURL('image/jpeg', 0.8);\n } finally {\n widgetHostEl.style.display = '';\n }\n}\n","export interface PageContext {\n url: string;\n title: string;\n viewportWidth: number;\n viewportHeight: number;\n userAgent: string;\n capturedAt: string;\n}\n\nexport function capturePageContext(): PageContext {\n return {\n url: window.location.href,\n title: document.title,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n userAgent: navigator.userAgent,\n capturedAt: new Date().toISOString(),\n };\n}\n","import type { PageContext } from './context';\n\nexport interface SubmitFeedbackOpts {\n apiKey: string;\n edgeFunctionUrl: string;\n comment: string;\n severity: 'critical' | 'major' | 'minor' | 'suggestion';\n screenshotDataUrl: string | null;\n context: PageContext;\n}\n\nexport async function submitFeedback(opts: SubmitFeedbackOpts): Promise<{ ok: boolean }> {\n const response = await fetch(opts.edgeFunctionUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-API-Key': opts.apiKey,\n },\n body: JSON.stringify({\n comment: opts.comment,\n severity: opts.severity,\n screenshot: opts.screenshotDataUrl,\n context: opts.context,\n }),\n });\n\n if (!response.ok) {\n throw new Error('Submit failed: ' + response.status);\n }\n\n return response.json();\n}\n","import React from 'react';\nimport { createRoot } from 'react-dom/client';\nimport { KonvaCanvas } from '@loupeink/ui';\nimport type { AnnotationShape } from '@loupeink/ui';\n\n// Full Tailwind + design-token CSS injected into Shadow DOM.\n// KonvaCanvas and KonvaToolbar use Tailwind utility classes and custom tokens\n// (bg-surface-1, border-border-default, etc.) that don't exist in the IIFE\n// bundle. We translate every class they use into explicit rules here.\nconst ANNOTATION_CSS = `\n /* ── Design tokens ─────────────────────────────────────── */\n .loupe-annotation-wrap {\n --color-surface-0: #09090b;\n --color-surface-1: #121215;\n --color-surface-2: #1a1a1f;\n --color-surface-3: #222228;\n --color-text-primary: #fafafa;\n --color-text-secondary: #a1a1aa;\n --color-border-default: #27272a;\n --color-accent: #34d399;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n color: var(--color-text-primary);\n box-sizing: border-box;\n }\n .loupe-annotation-wrap *, .loupe-annotation-wrap *::before, .loupe-annotation-wrap *::after {\n box-sizing: border-box;\n }\n\n /* ── Layout ─────────────────────────────────────────────── */\n .flex { display: flex; }\n .flex-col { flex-direction: column; }\n .flex-wrap { flex-wrap: wrap; }\n .flex-1 { flex: 1 1 0%; }\n .flex-shrink-0 { flex-shrink: 0; }\n .items-center { align-items: center; }\n .justify-between { justify-content: space-between; }\n .gap-1 { gap: 4px; }\n .gap-0\\\\.5 { gap: 2px; }\n .space-y-1 > * + * { margin-top: 4px; }\n .min-w-0 { min-width: 0; }\n\n /* ── Sizing ──────────────────────────────────────────────── */\n .h-full { height: 100%; }\n .h-4 { height: 16px; }\n .h-6 { height: 24px; }\n .h-8 { height: 32px; }\n .w-4 { width: 16px; }\n .w-6 { width: 24px; }\n .w-8 { width: 32px; }\n .w-px { width: 1px; }\n .w-72 { width: 288px; }\n\n /* ── Spacing ─────────────────────────────────────────────── */\n .p-0 { padding: 0; }\n .p-3 { padding: 12px; }\n .p-4 { padding: 16px; }\n .px-1\\\\.5 { padding-left: 6px; padding-right: 6px; }\n .px-2 { padding-left: 8px; padding-right: 8px; }\n .px-3 { padding-left: 12px; padding-right: 12px; }\n .px-4 { padding-left: 16px; padding-right: 16px; }\n .py-0\\\\.5 { padding-top: 2px; padding-bottom: 2px; }\n .py-2 { padding-top: 8px; padding-bottom: 8px; }\n .m-0 { margin: 0; }\n .mx-1 { margin-left: 4px; margin-right: 4px; }\n .mr-1 { margin-right: 4px; }\n .mt-1 { margin-top: 4px; }\n .mb-2 { margin-bottom: 8px; }\n\n /* ── Position ────────────────────────────────────────────── */\n .relative { position: relative; }\n .absolute { position: absolute; }\n .right-0 { right: 0; }\n .top-full { top: 100%; }\n .z-50 { z-index: 50; }\n .overflow-hidden { overflow: hidden; }\n\n /* ── Colours ─────────────────────────────────────────────── */\n .bg-surface-1 { background-color: var(--color-surface-1); }\n .bg-surface-2 { background-color: var(--color-surface-2); }\n .bg-surface-3 { background-color: var(--color-surface-3); }\n .bg-transparent { background-color: transparent; }\n .bg-accent { background-color: var(--color-accent); }\n .text-text-primary { color: var(--color-text-primary); }\n .text-text-secondary { color: var(--color-text-secondary); }\n .text-white { color: #fff; }\n\n /* ── Borders ─────────────────────────────────────────────── */\n .border { border-width: 1px; border-style: solid; }\n .border-b { border-bottom-width: 1px; border-bottom-style: solid; }\n .border-none { border: none; }\n .border-border-default { border-color: var(--color-border-default); }\n .border-accent { border-color: var(--color-accent); }\n .rounded { border-radius: 4px; }\n .rounded-full { border-radius: 9999px; }\n .rounded-lg { border-radius: 8px; }\n .shadow-lg { box-shadow: 0 10px 15px -3px rgba(0,0,0,0.4), 0 4px 6px -2px rgba(0,0,0,0.2); }\n\n /* ── Typography ──────────────────────────────────────────── */\n .text-xs { font-size: 12px; line-height: 16px; }\n .text-sm { font-size: 14px; line-height: 20px; }\n .font-mono { font-family: ui-monospace, monospace; }\n .font-semibold { font-weight: 600; }\n .font-bold { font-weight: 700; }\n .uppercase { text-transform: uppercase; }\n .tracking-wide { letter-spacing: 0.025em; }\n .outline-none { outline: none; }\n .resize-none { resize: none; }\n .transition-colors { transition: color 150ms, background-color 150ms, border-color 150ms; }\n\n /* ── Hover states ────────────────────────────────────────── */\n .hover\\\\:text-text-primary:hover { color: var(--color-text-primary); }\n .hover\\\\:bg-surface-2:hover { background-color: var(--color-surface-2); }\n\n /* ── Ring (active color swatch outline) ──────────────────── */\n .ring-2.ring-white.ring-offset-1 {\n box-shadow: 0 0 0 1px var(--color-surface-1), 0 0 0 3px #fff;\n }\n\n /* ── Button base reset (so host-page styles don't bleed) ─── */\n .loupe-annotation-wrap button {\n display: inline-flex; align-items: center; justify-content: center;\n cursor: pointer; border: 1px solid var(--color-border-default);\n background: var(--color-surface-2); color: var(--color-text-primary);\n font-family: inherit; font-size: 14px; line-height: 1;\n }\n .loupe-annotation-wrap button:hover {\n background: var(--color-surface-3);\n }\n`;\n\ninterface AnnotationCallbacks {\n onSave: (annotations: AnnotationShape[], annotatedDataUrl: string) => void;\n onCancel: () => void;\n}\n\n/**\n * Mount a KonvaCanvas annotation step inside the widget's Shadow DOM.\n *\n * Creates a full-screen overlay, renders KonvaCanvas with the provided\n * screenshot, and calls callbacks on save/cancel. React root and DOM nodes\n * are cleaned up after either action.\n */\nexport function mountAnnotationStep(\n shadow: ShadowRoot,\n screenshotUrl: string,\n callbacks: AnnotationCallbacks\n): void {\n // Inject annotation styles into the shadow root\n const styleEl = document.createElement('style');\n styleEl.textContent = ANNOTATION_CSS;\n shadow.appendChild(styleEl);\n\n // Create full-screen overlay container\n const container = document.createElement('div');\n container.className = 'loupe-annotation-wrap';\n container.style.cssText =\n 'position:fixed;inset:0;z-index:2147483647;background:rgba(0,0,0,0.85);display:flex;flex-direction:column;';\n shadow.appendChild(container);\n\n const root = createRoot(container);\n\n function cleanup() {\n root.unmount();\n container.remove();\n styleEl.remove();\n }\n\n root.render(\n React.createElement(KonvaCanvas, {\n screenshotUrl,\n existingAnnotations: [],\n onSave: (annotations: AnnotationShape[], dataUrl: string) => {\n cleanup();\n callbacks.onSave(annotations, dataUrl);\n },\n onCancel: () => {\n cleanup();\n callbacks.onCancel();\n },\n } as any)\n );\n}\n","import { WIDGET_CSS } from './styles';\nimport { captureScreenshot } from './screenshot';\nimport { capturePageContext } from './context';\nimport { submitFeedback } from './submit';\nimport { mountAnnotationStep } from './annotation-step';\n\nexport interface LoupeWidgetConfig {\n apiKey: string;\n edgeFunctionUrl?: string;\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n color?: string;\n buttonLabel?: string;\n}\n\nexport class LoupeWidget {\n private config: Required<LoupeWidgetConfig>;\n private hostEl: HTMLElement | null = null;\n private shadow: ShadowRoot | null = null;\n\n constructor(config: LoupeWidgetConfig) {\n this.config = {\n apiKey: config.apiKey,\n edgeFunctionUrl: config.edgeFunctionUrl ?? 'https://etdekhjnkevmrkgqixow.supabase.co/functions/v1/submit-sdk-feedback',\n position: config.position ?? 'bottom-right',\n color: config.color ?? '#10b981',\n buttonLabel: config.buttonLabel ?? 'Feedback',\n };\n }\n\n mount(container: HTMLElement): void {\n // Create shadow host\n this.hostEl = document.createElement('div');\n this.hostEl.id = 'loupe-widget-host';\n\n // Attach open shadow DOM (open mode allows test querying via shadowRoot)\n this.shadow = this.hostEl.attachShadow({ mode: 'open' });\n\n // Inject styles\n const style = document.createElement('style');\n style.textContent = WIDGET_CSS;\n this.shadow.appendChild(style);\n\n // Create feedback button\n const button = document.createElement('button');\n button.className = `loupe-btn ${this.config.position}`;\n button.textContent = this.config.buttonLabel;\n if (this.config.color) {\n button.style.backgroundColor = this.config.color;\n }\n\n // Create modal\n const modal = document.createElement('div');\n modal.className = 'loupe-modal';\n modal.setAttribute('data-loupe-modal', '');\n modal.setAttribute('aria-modal', 'true');\n modal.setAttribute('role', 'dialog');\n\n const textarea = document.createElement('textarea');\n textarea.placeholder = 'Describe the issue...';\n textarea.rows = 4;\n\n const select = document.createElement('select');\n const severities: Array<'critical' | 'major' | 'minor' | 'suggestion'> = ['critical', 'major', 'minor', 'suggestion'];\n for (const s of severities) {\n const opt = document.createElement('option');\n opt.value = s;\n opt.textContent = s.charAt(0).toUpperCase() + s.slice(1);\n select.appendChild(opt);\n }\n\n const actions = document.createElement('div');\n actions.className = 'loupe-modal-actions';\n\n const submitBtn = document.createElement('button');\n submitBtn.className = 'loupe-modal-submit';\n submitBtn.textContent = 'Send Feedback';\n\n const cancelBtn = document.createElement('button');\n cancelBtn.className = 'loupe-modal-cancel';\n cancelBtn.textContent = 'Cancel';\n\n actions.appendChild(cancelBtn);\n actions.appendChild(submitBtn);\n\n modal.appendChild(textarea);\n modal.appendChild(select);\n modal.appendChild(actions);\n\n // Append elements to shadow\n this.shadow.appendChild(button);\n this.shadow.appendChild(modal);\n\n // Append host to container\n container.appendChild(this.hostEl);\n\n // Wire up button click — toggle modal visibility\n button.addEventListener('click', () => {\n modal.classList.toggle('modal--visible');\n });\n\n // Wire up cancel\n cancelBtn.addEventListener('click', () => {\n modal.classList.remove('modal--visible');\n });\n\n // Wire up submit\n submitBtn.addEventListener('click', async () => {\n const comment = textarea.value.trim();\n const severity = select.value as 'critical' | 'major' | 'minor' | 'suggestion';\n const originalLabel = submitBtn.textContent;\n submitBtn.textContent = 'Capturing...';\n submitBtn.setAttribute('disabled', '');\n\n try {\n // 1. Take screenshot of the page\n const rawDataUrl = await captureScreenshot(this.hostEl!).catch(() => null);\n\n // 2. Hide the modal while annotation canvas is open\n modal.classList.remove('modal--visible');\n\n // 3. Open annotation step inside the shadow DOM\n mountAnnotationStep(this.shadow!, rawDataUrl ?? '', {\n onSave: async (_annotations, annotatedDataUrl) => {\n // 4. Submit with annotated screenshot\n const context = capturePageContext();\n try {\n await submitFeedback({\n apiKey: this.config.apiKey,\n edgeFunctionUrl: this.config.edgeFunctionUrl,\n comment,\n severity,\n screenshotDataUrl: annotatedDataUrl,\n context,\n });\n textarea.value = '';\n } catch {\n alert('Failed to send feedback. Please try again.');\n } finally {\n submitBtn.textContent = originalLabel;\n submitBtn.removeAttribute('disabled');\n }\n },\n onCancel: () => {\n // User cancelled annotation — restore modal\n modal.classList.add('modal--visible');\n submitBtn.textContent = originalLabel;\n submitBtn.removeAttribute('disabled');\n },\n });\n } catch {\n alert('Failed to capture screenshot. Please try again.');\n submitBtn.textContent = originalLabel;\n submitBtn.removeAttribute('disabled');\n }\n });\n }\n\n destroy(): void {\n this.hostEl?.remove();\n this.hostEl = null;\n this.shadow = null;\n }\n}\n","import { LoupeWidget, type LoupeWidgetConfig } from './widget';\n\nexport { LoupeWidget };\nexport type { LoupeWidgetConfig };\nexport { capturePageContext } from './context';\nexport { submitFeedback } from './submit';\nexport { captureScreenshot } from './screenshot';\n\nlet currentWidget: LoupeWidget | null = null;\n\nexport function init(config: LoupeWidgetConfig): void {\n currentWidget = new LoupeWidget(config);\n currentWidget.mount(document.body);\n}\n\nexport function destroy(): void {\n currentWidget?.destroy();\n currentWidget = null;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@loupeink/web-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": { "access": "public" },
|
|
21
|
+
"files": ["dist"],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"test": "vitest",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/loupeink/web-sdk"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"html2canvas-pro": "^1.0.0",
|
|
33
|
+
"@loupeink/ui": "*",
|
|
34
|
+
"react": "^19.0.0",
|
|
35
|
+
"react-dom": "^19.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/react": "^19.0.0",
|
|
39
|
+
"@types/react-dom": "^19.0.0",
|
|
40
|
+
"@vitest/coverage-v8": "^2.0.0",
|
|
41
|
+
"jsdom": "^25.0.0",
|
|
42
|
+
"tsup": "^8.0.0",
|
|
43
|
+
"typescript": "~5.7.0",
|
|
44
|
+
"vitest": "^2.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|