@villetorio/lms-feedback-widget 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/README.md +268 -0
- package/dist/FeedbackWidget.d.ts +18 -0
- package/dist/index.cjs +250 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +248 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.js +256 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# @villetorio/lms-feedback-widget
|
|
2
|
+
|
|
3
|
+
> Framework-agnostic floating feedback widget. Works in Sapper, SvelteKit, Next.js, React, Vue, and plain HTML — zero framework dependencies.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ⭐ Star rating (1–5)
|
|
10
|
+
- 💬 Optional message textarea
|
|
11
|
+
- ✅ Success / error states
|
|
12
|
+
- ♿ Accessible (ARIA labels, keyboard support)
|
|
13
|
+
- 🌙 Click-outside to close
|
|
14
|
+
- 📦 Zero framework dependencies — pure TypeScript
|
|
15
|
+
- 🔌 Works in **any** JS project
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @villetorio/lms-feedback-widget
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For projects with peer dependency conflicts (e.g. old Svelte 3 / Rollup 1):
|
|
26
|
+
```bash
|
|
27
|
+
npm install @villetorio/lms-feedback-widget --legacy-peer-deps
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
> ⚠️ Always call `FeedbackWidget()` inside a **mount/effect hook** — never at the top level — because it needs `document.body` to exist first.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
### Sapper
|
|
39
|
+
|
|
40
|
+
```svelte
|
|
41
|
+
<!-- src/routes/_layout.svelte -->
|
|
42
|
+
<script>
|
|
43
|
+
import { onMount } from 'svelte';
|
|
44
|
+
import { FeedbackWidget } from '@villetorio/lms-feedback-widget';
|
|
45
|
+
|
|
46
|
+
onMount(() => {
|
|
47
|
+
FeedbackWidget({ apiRoute: '/api/feedback', appName: 'My App' });
|
|
48
|
+
});
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<slot />
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
API endpoint:
|
|
55
|
+
```js
|
|
56
|
+
// src/routes/api/feedback.js
|
|
57
|
+
export async function post(req, res) {
|
|
58
|
+
const payload = req.body;
|
|
59
|
+
console.log('[Feedback]', payload);
|
|
60
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
61
|
+
res.end(JSON.stringify({ ok: true }));
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### SvelteKit
|
|
68
|
+
|
|
69
|
+
```svelte
|
|
70
|
+
<!-- src/routes/+layout.svelte -->
|
|
71
|
+
<script>
|
|
72
|
+
import { onMount } from 'svelte';
|
|
73
|
+
import { FeedbackWidget } from '@villetorio/lms-feedback-widget';
|
|
74
|
+
|
|
75
|
+
onMount(() => {
|
|
76
|
+
FeedbackWidget({ apiRoute: '/api/feedback', appName: 'My App' });
|
|
77
|
+
});
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<slot />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
API endpoint:
|
|
84
|
+
```ts
|
|
85
|
+
// src/routes/api/feedback/+server.ts
|
|
86
|
+
import { json } from '@sveltejs/kit';
|
|
87
|
+
import type { RequestHandler } from './$types';
|
|
88
|
+
|
|
89
|
+
export const POST: RequestHandler = async ({ request }) => {
|
|
90
|
+
const payload = await request.json();
|
|
91
|
+
console.log('[Feedback]', payload);
|
|
92
|
+
return json({ ok: true });
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### Next.js (App Router)
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
// src/components/FeedbackWidgetLoader.tsx
|
|
102
|
+
'use client';
|
|
103
|
+
|
|
104
|
+
import { useEffect } from 'react';
|
|
105
|
+
import { FeedbackWidget } from '@villetorio/lms-feedback-widget';
|
|
106
|
+
|
|
107
|
+
export default function FeedbackWidgetLoader() {
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
FeedbackWidget({ apiRoute: '/api/feedback', appName: 'My App' });
|
|
110
|
+
}, []);
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// src/app/layout.tsx
|
|
118
|
+
import FeedbackWidgetLoader from '@/components/FeedbackWidgetLoader';
|
|
119
|
+
|
|
120
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
121
|
+
return (
|
|
122
|
+
<html lang="en">
|
|
123
|
+
<body>
|
|
124
|
+
{children}
|
|
125
|
+
<FeedbackWidgetLoader />
|
|
126
|
+
</body>
|
|
127
|
+
</html>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
API endpoint:
|
|
133
|
+
```ts
|
|
134
|
+
// src/app/api/feedback/route.ts
|
|
135
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
136
|
+
|
|
137
|
+
export async function POST(req: NextRequest) {
|
|
138
|
+
const payload = await req.json();
|
|
139
|
+
console.log('[Feedback]', payload);
|
|
140
|
+
return NextResponse.json({ ok: true });
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### React
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
// FeedbackWidgetLoader.tsx
|
|
150
|
+
import { useEffect } from 'react';
|
|
151
|
+
import { FeedbackWidget } from '@villetorio/lms-feedback-widget';
|
|
152
|
+
|
|
153
|
+
export default function FeedbackWidgetLoader() {
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
FeedbackWidget({ apiRoute: '/api/feedback', appName: 'My App' });
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
// App.tsx
|
|
164
|
+
import FeedbackWidgetLoader from './FeedbackWidgetLoader';
|
|
165
|
+
|
|
166
|
+
export default function App() {
|
|
167
|
+
return (
|
|
168
|
+
<>
|
|
169
|
+
<YourRoutes />
|
|
170
|
+
<FeedbackWidgetLoader />
|
|
171
|
+
</>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
### Vue
|
|
179
|
+
|
|
180
|
+
```vue
|
|
181
|
+
<!-- App.vue or a layout component -->
|
|
182
|
+
<script setup>
|
|
183
|
+
import { onMounted } from 'vue';
|
|
184
|
+
import { FeedbackWidget } from '@villetorio/lms-feedback-widget';
|
|
185
|
+
|
|
186
|
+
onMounted(() => {
|
|
187
|
+
FeedbackWidget({ apiRoute: '/api/feedback', appName: 'My App' });
|
|
188
|
+
});
|
|
189
|
+
</script>
|
|
190
|
+
|
|
191
|
+
<template>
|
|
192
|
+
<RouterView />
|
|
193
|
+
</template>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
### Plain HTML (no bundler)
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<script src="node_modules/@villetorio/lms-feedback-widget/dist/index.umd.js"></script>
|
|
202
|
+
<script>
|
|
203
|
+
LMSFeedbackWidget.FeedbackWidget({
|
|
204
|
+
apiRoute: '/api/feedback',
|
|
205
|
+
appName: 'My App'
|
|
206
|
+
});
|
|
207
|
+
</script>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Or via CDN:
|
|
211
|
+
```html
|
|
212
|
+
<script src="https://unpkg.com/@villetorio/lms-feedback-widget/dist/index.umd.js"></script>
|
|
213
|
+
<script>
|
|
214
|
+
LMSFeedbackWidget.FeedbackWidget({ apiRoute: '/api/feedback', appName: 'My App' });
|
|
215
|
+
</script>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Options
|
|
221
|
+
|
|
222
|
+
| Option | Type | Default | Description |
|
|
223
|
+
|---|---|---|---|
|
|
224
|
+
| `apiRoute` | `string` | `"/api/feedback"` | URL to POST feedback payload to |
|
|
225
|
+
| `apiKey` | `string` | `""` | Optional Bearer token for Authorization header |
|
|
226
|
+
| `title` | `string` | `"Share Your Feedback"` | Panel heading |
|
|
227
|
+
| `subtitle` | `string` | `"Help us improve"` | Panel subheading |
|
|
228
|
+
| `appName` | `string` | `"App"` | Included in payload and success message |
|
|
229
|
+
| `onSuccess` | `(payload: FeedbackPayload) => void` | `undefined` | Called after successful submission |
|
|
230
|
+
| `onError` | `(error: Error) => void` | `undefined` | Called when submission fails |
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Payload Shape
|
|
235
|
+
|
|
236
|
+
The widget POSTs this JSON to your `apiRoute`:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
interface FeedbackPayload {
|
|
240
|
+
rating: number; // 1–5 stars
|
|
241
|
+
message: string; // optional user text
|
|
242
|
+
app: string; // from appName option
|
|
243
|
+
timestamp: string; // ISO 8601 e.g. "2026-03-04T07:00:00.000Z"
|
|
244
|
+
url: string; // current page URL
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Mount Hook Reference
|
|
251
|
+
|
|
252
|
+
| Framework | Hook |
|
|
253
|
+
|---|---|
|
|
254
|
+
| Svelte / Sapper | `onMount(() => { FeedbackWidget({...}) })` |
|
|
255
|
+
| SvelteKit | `onMount(() => { FeedbackWidget({...}) })` |
|
|
256
|
+
| React / Next.js | `useEffect(() => { FeedbackWidget({...}) }, [])` |
|
|
257
|
+
| Vue | `onMounted(() => { FeedbackWidget({...}) })` |
|
|
258
|
+
| Plain HTML | Bottom of `<body>` or `DOMContentLoaded` |
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Build Outputs
|
|
263
|
+
|
|
264
|
+
| File | Format | Use case |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| `dist/index.js` | ESM | SvelteKit, Vite, modern bundlers |
|
|
267
|
+
| `dist/index.cjs` | CJS | Node.js, Sapper, Rollup 1, Jest |
|
|
268
|
+
| `dist/index.umd.js` | UMD | Plain `<script>` tag, CDN |
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface FeedbackPayload {
|
|
2
|
+
rating: number;
|
|
3
|
+
message: string;
|
|
4
|
+
app: string;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
url: string;
|
|
7
|
+
}
|
|
8
|
+
interface FeedbackWidgetOptions {
|
|
9
|
+
apiRoute?: string;
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
subtitle?: string;
|
|
13
|
+
appName?: string;
|
|
14
|
+
onSuccess?: (payload: FeedbackPayload) => void;
|
|
15
|
+
onError?: (error: Error) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function FeedbackWidget(opts?: FeedbackWidgetOptions): void;
|
|
18
|
+
export {};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const CSS = `
|
|
4
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
|
|
5
|
+
.fw2-wrap { position:fixed; bottom:24px; right:24px; z-index:9999; font-family:'Inter',sans-serif; }
|
|
6
|
+
.fw2-fab {
|
|
7
|
+
display:flex; align-items:center; gap:8px;
|
|
8
|
+
background:#2c3e6b; color:#fff; border:none; cursor:pointer;
|
|
9
|
+
border-radius:24px; padding:12px 20px; font-size:14px; font-weight:500;
|
|
10
|
+
box-shadow:0 4px 16px rgba(0,0,0,0.25); transition:background .2s,transform .15s;
|
|
11
|
+
font-family:'Inter',sans-serif;
|
|
12
|
+
}
|
|
13
|
+
.fw2-fab:hover { background:#1e2e55; transform:translateY(-1px); }
|
|
14
|
+
.fw2-panel {
|
|
15
|
+
background:#fff; border-radius:12px; box-shadow:0 8px 40px rgba(0,0,0,0.18);
|
|
16
|
+
width:320px; overflow:hidden; margin-bottom:12px;
|
|
17
|
+
animation:fw2-pop .2s cubic-bezier(.34,1.4,.64,1) both;
|
|
18
|
+
}
|
|
19
|
+
@keyframes fw2-pop { from{opacity:0;transform:scale(.9) translateY(10px)} to{opacity:1;transform:scale(1) translateY(0)} }
|
|
20
|
+
.fw2-header {
|
|
21
|
+
background:#2c3e6b; color:#fff; padding:16px 18px 14px;
|
|
22
|
+
display:flex; align-items:flex-start; justify-content:space-between;
|
|
23
|
+
}
|
|
24
|
+
.fw2-header-text h3 { font-size:15px; font-weight:600; margin:0 0 3px; line-height:1.3; color:#fff; }
|
|
25
|
+
.fw2-header-text p { font-size:12px; opacity:.75; margin:0; }
|
|
26
|
+
.fw2-close-x { background:none; border:none; color:#fff; cursor:pointer; font-size:22px; line-height:1; opacity:.8; padding:0; margin-left:8px; }
|
|
27
|
+
.fw2-close-x:hover { opacity:1; }
|
|
28
|
+
.fw2-body { padding:20px 18px 18px; }
|
|
29
|
+
.fw2-question { font-size:13.5px; color:#333; text-align:center; margin-bottom:14px; }
|
|
30
|
+
.fw2-stars { display:flex; justify-content:center; gap:8px; margin-bottom:18px; }
|
|
31
|
+
.fw2-star {
|
|
32
|
+
background:none; border:none; padding:0; cursor:pointer; font-size:28px; line-height:1;
|
|
33
|
+
color:transparent; -webkit-text-stroke:1.5px #aaa; transition:-webkit-text-stroke .1s,color .1s,transform .1s;
|
|
34
|
+
}
|
|
35
|
+
.fw2-star:hover,.fw2-star.hover { -webkit-text-stroke:1.5px #c9a227; transform:scale(1.15); }
|
|
36
|
+
.fw2-star.active { -webkit-text-stroke:1.5px #c9a227; color:#c9a227; }
|
|
37
|
+
.fw2-textarea-label { font-size:13px; color:#444; margin-bottom:6px; display:block; }
|
|
38
|
+
.fw2-textarea-label span { color:#888; font-size:12px; }
|
|
39
|
+
.fw2-textarea {
|
|
40
|
+
width:100%; border:1px solid #d0d5dd; border-radius:6px; padding:10px 12px;
|
|
41
|
+
font-family:'Inter',sans-serif; font-size:13px; color:#333; resize:vertical; min-height:88px;
|
|
42
|
+
outline:none; transition:border-color .2s; background:#fff; margin-bottom:14px; box-sizing:border-box;
|
|
43
|
+
}
|
|
44
|
+
.fw2-textarea:focus { border-color:#2c3e6b; }
|
|
45
|
+
.fw2-submit {
|
|
46
|
+
width:100%; background:#6b7fa8; color:#fff; border:none; border-radius:6px; padding:11px;
|
|
47
|
+
font-family:'Inter',sans-serif; font-size:14px; font-weight:500; cursor:pointer;
|
|
48
|
+
display:flex; align-items:center; justify-content:center; gap:8px; transition:background .2s;
|
|
49
|
+
}
|
|
50
|
+
.fw2-submit:hover:not(:disabled) { background:#2c3e6b; }
|
|
51
|
+
.fw2-submit:disabled { opacity:.6; cursor:not-allowed; }
|
|
52
|
+
.fw2-error { font-size:12px; color:#dc2626; margin-bottom:10px; text-align:center; }
|
|
53
|
+
.fw2-success { padding:28px 18px 22px; text-align:center; }
|
|
54
|
+
.fw2-success-icon {
|
|
55
|
+
width:60px; height:60px; border-radius:50%; border:3px solid #c9a227;
|
|
56
|
+
display:flex; align-items:center; justify-content:center; margin:0 auto 16px; color:#c9a227;
|
|
57
|
+
}
|
|
58
|
+
.fw2-success h4 { font-size:18px; font-weight:600; color:#222; margin:0 0 8px; }
|
|
59
|
+
.fw2-success p { font-size:13px; color:#666; margin:0 0 20px; line-height:1.5; }
|
|
60
|
+
.fw2-submit-another {
|
|
61
|
+
background:#fff; border:1.5px solid #ccc; border-radius:6px; padding:9px 20px;
|
|
62
|
+
font-family:'Inter',sans-serif; font-size:13px; font-weight:500; color:#444; cursor:pointer;
|
|
63
|
+
}
|
|
64
|
+
.fw2-submit-another:hover { border-color:#2c3e6b; color:#2c3e6b; }
|
|
65
|
+
.fw2-close-pill { display:flex; align-items:center; justify-content:flex-end; margin-top:8px; }
|
|
66
|
+
.fw2-close-pill button {
|
|
67
|
+
background:#fff; border:1.5px solid #d0d5dd; border-radius:20px; padding:6px 16px;
|
|
68
|
+
font-family:'Inter',sans-serif; font-size:13px; color:#555; cursor:pointer;
|
|
69
|
+
display:flex; align-items:center; gap:6px; box-shadow:0 2px 8px rgba(0,0,0,0.08);
|
|
70
|
+
}
|
|
71
|
+
.fw2-close-pill button:hover { border-color:#999; }
|
|
72
|
+
`;
|
|
73
|
+
const ICON_SEND = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>`;
|
|
74
|
+
const ICON_CHAT = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>`;
|
|
75
|
+
const ICON_CHECK = `<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
|
|
76
|
+
const ICON_CLOSE = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
|
77
|
+
function FeedbackWidget(opts = {}) {
|
|
78
|
+
const { apiRoute = '/api/feedback', apiKey = '', title = 'Share Your Feedback', subtitle = 'Help us improve', appName = 'App', onSuccess, onError, } = opts;
|
|
79
|
+
// ── Inject styles once ──────────────────────────────────────────────────
|
|
80
|
+
if (!document.getElementById('fw2-styles')) {
|
|
81
|
+
const style = document.createElement('style');
|
|
82
|
+
style.id = 'fw2-styles';
|
|
83
|
+
style.textContent = CSS;
|
|
84
|
+
document.head.appendChild(style);
|
|
85
|
+
}
|
|
86
|
+
// ── State ───────────────────────────────────────────────────────────────
|
|
87
|
+
let open = false;
|
|
88
|
+
let rating = 0;
|
|
89
|
+
let hoverRating = 0;
|
|
90
|
+
let message = '';
|
|
91
|
+
let status = 'idle';
|
|
92
|
+
let errorMsg = '';
|
|
93
|
+
// ── Root element ────────────────────────────────────────────────────────
|
|
94
|
+
const wrap = document.createElement('div');
|
|
95
|
+
wrap.className = 'fw2-wrap';
|
|
96
|
+
document.body.appendChild(wrap);
|
|
97
|
+
// ── Render ───────────────────────────────────────────────────────────────
|
|
98
|
+
function render() {
|
|
99
|
+
wrap.innerHTML = open ? renderPanel() : renderFab();
|
|
100
|
+
bindEvents();
|
|
101
|
+
}
|
|
102
|
+
function renderFab() {
|
|
103
|
+
return `<button class="fw2-fab" id="fw2-fab-btn" aria-label="Open feedback">${ICON_CHAT} Feedback</button>`;
|
|
104
|
+
}
|
|
105
|
+
function renderPanel() {
|
|
106
|
+
return `
|
|
107
|
+
<div>
|
|
108
|
+
<div class="fw2-panel">
|
|
109
|
+
<div class="fw2-header">
|
|
110
|
+
<div class="fw2-header-text">
|
|
111
|
+
<h3>${title}</h3>
|
|
112
|
+
<p>${subtitle}</p>
|
|
113
|
+
</div>
|
|
114
|
+
<button class="fw2-close-x" id="fw2-close-x" aria-label="Close">×</button>
|
|
115
|
+
</div>
|
|
116
|
+
${status === 'success' ? renderSuccess() : renderForm()}
|
|
117
|
+
</div>
|
|
118
|
+
<div class="fw2-close-pill">
|
|
119
|
+
<button id="fw2-close-pill">${ICON_CLOSE} Close</button>
|
|
120
|
+
</div>
|
|
121
|
+
</div>`;
|
|
122
|
+
}
|
|
123
|
+
function renderSuccess() {
|
|
124
|
+
return `
|
|
125
|
+
<div class="fw2-success">
|
|
126
|
+
<div class="fw2-success-icon">${ICON_CHECK}</div>
|
|
127
|
+
<h4>Thank You!</h4>
|
|
128
|
+
<p>Your feedback helps us improve the<br/>${appName} experience.</p>
|
|
129
|
+
<button class="fw2-submit-another" id="fw2-another">Submit Another</button>
|
|
130
|
+
</div>`;
|
|
131
|
+
}
|
|
132
|
+
function renderForm() {
|
|
133
|
+
const stars = [1, 2, 3, 4, 5].map(s => `
|
|
134
|
+
<button class="fw2-star${s <= rating ? ' active' : ''}"
|
|
135
|
+
data-star="${s}" aria-label="${s} star${s > 1 ? 's' : ''}" aria-pressed="${s <= rating}">★</button>
|
|
136
|
+
`).join('');
|
|
137
|
+
return `
|
|
138
|
+
<div class="fw2-body">
|
|
139
|
+
<p class="fw2-question">How would you rate your experience?</p>
|
|
140
|
+
<div class="fw2-stars" role="radiogroup" aria-label="Star rating">${stars}</div>
|
|
141
|
+
<label class="fw2-textarea-label" for="fw2-message">Tell us more <span>(optional)</span></label>
|
|
142
|
+
<textarea id="fw2-message" class="fw2-textarea" placeholder="Share your thoughts...">${message}</textarea>
|
|
143
|
+
${errorMsg ? `<div class="fw2-error" role="alert">${errorMsg}</div>` : ''}
|
|
144
|
+
<button class="fw2-submit" id="fw2-submit" ${status === 'loading' ? 'disabled' : ''}>
|
|
145
|
+
${ICON_SEND} ${status === 'loading' ? 'Submitting…' : 'Submit Feedback'}
|
|
146
|
+
</button>
|
|
147
|
+
</div>`;
|
|
148
|
+
}
|
|
149
|
+
// ── Update star states ──────────────────────────────────────────────────
|
|
150
|
+
function updateStarStates() {
|
|
151
|
+
wrap.querySelectorAll('.fw2-star').forEach(btn => {
|
|
152
|
+
const starValue = Number(btn.dataset.star);
|
|
153
|
+
const isActive = starValue <= rating;
|
|
154
|
+
const isHover = starValue <= hoverRating && starValue > rating;
|
|
155
|
+
btn.classList.toggle('active', isActive);
|
|
156
|
+
btn.classList.toggle('hover', isHover);
|
|
157
|
+
btn.setAttribute('aria-pressed', String(isActive));
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// ── Event binding ────────────────────────────────────────────────────────
|
|
161
|
+
function bindEvents() {
|
|
162
|
+
var _a, _b, _c, _d, _e;
|
|
163
|
+
// FAB
|
|
164
|
+
(_a = wrap.querySelector('#fw2-fab-btn')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => { open = true; render(); });
|
|
165
|
+
// Close buttons
|
|
166
|
+
(_b = wrap.querySelector('#fw2-close-x')) === null || _b === void 0 ? void 0 : _b.addEventListener('click', () => { open = false; render(); });
|
|
167
|
+
(_c = wrap.querySelector('#fw2-close-pill')) === null || _c === void 0 ? void 0 : _c.addEventListener('click', () => { open = false; render(); });
|
|
168
|
+
// Submit another
|
|
169
|
+
(_d = wrap.querySelector('#fw2-another')) === null || _d === void 0 ? void 0 : _d.addEventListener('click', () => {
|
|
170
|
+
rating = 0;
|
|
171
|
+
hoverRating = 0;
|
|
172
|
+
message = '';
|
|
173
|
+
status = 'idle';
|
|
174
|
+
errorMsg = '';
|
|
175
|
+
render();
|
|
176
|
+
});
|
|
177
|
+
// Stars
|
|
178
|
+
wrap.querySelectorAll('.fw2-star').forEach(btn => {
|
|
179
|
+
btn.addEventListener('click', () => {
|
|
180
|
+
rating = Number(btn.dataset.star);
|
|
181
|
+
updateStarStates();
|
|
182
|
+
});
|
|
183
|
+
btn.addEventListener('mouseenter', () => {
|
|
184
|
+
hoverRating = Number(btn.dataset.star);
|
|
185
|
+
updateStarStates();
|
|
186
|
+
});
|
|
187
|
+
btn.addEventListener('mouseleave', () => {
|
|
188
|
+
hoverRating = 0;
|
|
189
|
+
updateStarStates();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
// Textarea — preserve value without full re-render
|
|
193
|
+
const ta = wrap.querySelector('#fw2-message');
|
|
194
|
+
ta === null || ta === void 0 ? void 0 : ta.addEventListener('input', () => { message = ta.value; });
|
|
195
|
+
// Submit
|
|
196
|
+
(_e = wrap.querySelector('#fw2-submit')) === null || _e === void 0 ? void 0 : _e.addEventListener('click', () => void handleSubmit());
|
|
197
|
+
// Click outside to close
|
|
198
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
199
|
+
}
|
|
200
|
+
function handleClickOutside(e) {
|
|
201
|
+
if (open && !wrap.contains(e.target)) {
|
|
202
|
+
open = false;
|
|
203
|
+
render();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// ── Submit ───────────────────────────────────────────────────────────────
|
|
207
|
+
async function handleSubmit() {
|
|
208
|
+
if (!rating) {
|
|
209
|
+
errorMsg = 'Please select a star rating before submitting.';
|
|
210
|
+
render();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
status = 'loading';
|
|
214
|
+
errorMsg = '';
|
|
215
|
+
render();
|
|
216
|
+
const payload = {
|
|
217
|
+
rating,
|
|
218
|
+
message,
|
|
219
|
+
app: appName,
|
|
220
|
+
timestamp: new Date().toISOString(),
|
|
221
|
+
url: typeof window !== 'undefined' ? window.location.href : '',
|
|
222
|
+
};
|
|
223
|
+
try {
|
|
224
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
225
|
+
if (apiKey)
|
|
226
|
+
headers['Authorization'] = `Basic ${apiKey}`;
|
|
227
|
+
const res = await fetch(apiRoute, {
|
|
228
|
+
method: 'POST',
|
|
229
|
+
headers,
|
|
230
|
+
body: JSON.stringify(payload),
|
|
231
|
+
});
|
|
232
|
+
if (!res.ok)
|
|
233
|
+
throw new Error(`HTTP ${res.status}`);
|
|
234
|
+
status = 'success';
|
|
235
|
+
onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess(payload);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
const error = err instanceof Error ? err : new Error('Unknown error');
|
|
239
|
+
status = 'error';
|
|
240
|
+
errorMsg = 'Something went wrong. Please try again.';
|
|
241
|
+
onError === null || onError === void 0 ? void 0 : onError(error);
|
|
242
|
+
}
|
|
243
|
+
render();
|
|
244
|
+
}
|
|
245
|
+
// ── Init ─────────────────────────────────────────────────────────────────
|
|
246
|
+
render();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
exports.FeedbackWidget = FeedbackWidget;
|
|
250
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/FeedbackWidget.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
const CSS = `
|
|
2
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
|
|
3
|
+
.fw2-wrap { position:fixed; bottom:24px; right:24px; z-index:9999; font-family:'Inter',sans-serif; }
|
|
4
|
+
.fw2-fab {
|
|
5
|
+
display:flex; align-items:center; gap:8px;
|
|
6
|
+
background:#2c3e6b; color:#fff; border:none; cursor:pointer;
|
|
7
|
+
border-radius:24px; padding:12px 20px; font-size:14px; font-weight:500;
|
|
8
|
+
box-shadow:0 4px 16px rgba(0,0,0,0.25); transition:background .2s,transform .15s;
|
|
9
|
+
font-family:'Inter',sans-serif;
|
|
10
|
+
}
|
|
11
|
+
.fw2-fab:hover { background:#1e2e55; transform:translateY(-1px); }
|
|
12
|
+
.fw2-panel {
|
|
13
|
+
background:#fff; border-radius:12px; box-shadow:0 8px 40px rgba(0,0,0,0.18);
|
|
14
|
+
width:320px; overflow:hidden; margin-bottom:12px;
|
|
15
|
+
animation:fw2-pop .2s cubic-bezier(.34,1.4,.64,1) both;
|
|
16
|
+
}
|
|
17
|
+
@keyframes fw2-pop { from{opacity:0;transform:scale(.9) translateY(10px)} to{opacity:1;transform:scale(1) translateY(0)} }
|
|
18
|
+
.fw2-header {
|
|
19
|
+
background:#2c3e6b; color:#fff; padding:16px 18px 14px;
|
|
20
|
+
display:flex; align-items:flex-start; justify-content:space-between;
|
|
21
|
+
}
|
|
22
|
+
.fw2-header-text h3 { font-size:15px; font-weight:600; margin:0 0 3px; line-height:1.3; color:#fff; }
|
|
23
|
+
.fw2-header-text p { font-size:12px; opacity:.75; margin:0; }
|
|
24
|
+
.fw2-close-x { background:none; border:none; color:#fff; cursor:pointer; font-size:22px; line-height:1; opacity:.8; padding:0; margin-left:8px; }
|
|
25
|
+
.fw2-close-x:hover { opacity:1; }
|
|
26
|
+
.fw2-body { padding:20px 18px 18px; }
|
|
27
|
+
.fw2-question { font-size:13.5px; color:#333; text-align:center; margin-bottom:14px; }
|
|
28
|
+
.fw2-stars { display:flex; justify-content:center; gap:8px; margin-bottom:18px; }
|
|
29
|
+
.fw2-star {
|
|
30
|
+
background:none; border:none; padding:0; cursor:pointer; font-size:28px; line-height:1;
|
|
31
|
+
color:transparent; -webkit-text-stroke:1.5px #aaa; transition:-webkit-text-stroke .1s,color .1s,transform .1s;
|
|
32
|
+
}
|
|
33
|
+
.fw2-star:hover,.fw2-star.hover { -webkit-text-stroke:1.5px #c9a227; transform:scale(1.15); }
|
|
34
|
+
.fw2-star.active { -webkit-text-stroke:1.5px #c9a227; color:#c9a227; }
|
|
35
|
+
.fw2-textarea-label { font-size:13px; color:#444; margin-bottom:6px; display:block; }
|
|
36
|
+
.fw2-textarea-label span { color:#888; font-size:12px; }
|
|
37
|
+
.fw2-textarea {
|
|
38
|
+
width:100%; border:1px solid #d0d5dd; border-radius:6px; padding:10px 12px;
|
|
39
|
+
font-family:'Inter',sans-serif; font-size:13px; color:#333; resize:vertical; min-height:88px;
|
|
40
|
+
outline:none; transition:border-color .2s; background:#fff; margin-bottom:14px; box-sizing:border-box;
|
|
41
|
+
}
|
|
42
|
+
.fw2-textarea:focus { border-color:#2c3e6b; }
|
|
43
|
+
.fw2-submit {
|
|
44
|
+
width:100%; background:#6b7fa8; color:#fff; border:none; border-radius:6px; padding:11px;
|
|
45
|
+
font-family:'Inter',sans-serif; font-size:14px; font-weight:500; cursor:pointer;
|
|
46
|
+
display:flex; align-items:center; justify-content:center; gap:8px; transition:background .2s;
|
|
47
|
+
}
|
|
48
|
+
.fw2-submit:hover:not(:disabled) { background:#2c3e6b; }
|
|
49
|
+
.fw2-submit:disabled { opacity:.6; cursor:not-allowed; }
|
|
50
|
+
.fw2-error { font-size:12px; color:#dc2626; margin-bottom:10px; text-align:center; }
|
|
51
|
+
.fw2-success { padding:28px 18px 22px; text-align:center; }
|
|
52
|
+
.fw2-success-icon {
|
|
53
|
+
width:60px; height:60px; border-radius:50%; border:3px solid #c9a227;
|
|
54
|
+
display:flex; align-items:center; justify-content:center; margin:0 auto 16px; color:#c9a227;
|
|
55
|
+
}
|
|
56
|
+
.fw2-success h4 { font-size:18px; font-weight:600; color:#222; margin:0 0 8px; }
|
|
57
|
+
.fw2-success p { font-size:13px; color:#666; margin:0 0 20px; line-height:1.5; }
|
|
58
|
+
.fw2-submit-another {
|
|
59
|
+
background:#fff; border:1.5px solid #ccc; border-radius:6px; padding:9px 20px;
|
|
60
|
+
font-family:'Inter',sans-serif; font-size:13px; font-weight:500; color:#444; cursor:pointer;
|
|
61
|
+
}
|
|
62
|
+
.fw2-submit-another:hover { border-color:#2c3e6b; color:#2c3e6b; }
|
|
63
|
+
.fw2-close-pill { display:flex; align-items:center; justify-content:flex-end; margin-top:8px; }
|
|
64
|
+
.fw2-close-pill button {
|
|
65
|
+
background:#fff; border:1.5px solid #d0d5dd; border-radius:20px; padding:6px 16px;
|
|
66
|
+
font-family:'Inter',sans-serif; font-size:13px; color:#555; cursor:pointer;
|
|
67
|
+
display:flex; align-items:center; gap:6px; box-shadow:0 2px 8px rgba(0,0,0,0.08);
|
|
68
|
+
}
|
|
69
|
+
.fw2-close-pill button:hover { border-color:#999; }
|
|
70
|
+
`;
|
|
71
|
+
const ICON_SEND = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>`;
|
|
72
|
+
const ICON_CHAT = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>`;
|
|
73
|
+
const ICON_CHECK = `<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
|
|
74
|
+
const ICON_CLOSE = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
|
75
|
+
function FeedbackWidget(opts = {}) {
|
|
76
|
+
const { apiRoute = '/api/feedback', apiKey = '', title = 'Share Your Feedback', subtitle = 'Help us improve', appName = 'App', onSuccess, onError, } = opts;
|
|
77
|
+
// ── Inject styles once ──────────────────────────────────────────────────
|
|
78
|
+
if (!document.getElementById('fw2-styles')) {
|
|
79
|
+
const style = document.createElement('style');
|
|
80
|
+
style.id = 'fw2-styles';
|
|
81
|
+
style.textContent = CSS;
|
|
82
|
+
document.head.appendChild(style);
|
|
83
|
+
}
|
|
84
|
+
// ── State ───────────────────────────────────────────────────────────────
|
|
85
|
+
let open = false;
|
|
86
|
+
let rating = 0;
|
|
87
|
+
let hoverRating = 0;
|
|
88
|
+
let message = '';
|
|
89
|
+
let status = 'idle';
|
|
90
|
+
let errorMsg = '';
|
|
91
|
+
// ── Root element ────────────────────────────────────────────────────────
|
|
92
|
+
const wrap = document.createElement('div');
|
|
93
|
+
wrap.className = 'fw2-wrap';
|
|
94
|
+
document.body.appendChild(wrap);
|
|
95
|
+
// ── Render ───────────────────────────────────────────────────────────────
|
|
96
|
+
function render() {
|
|
97
|
+
wrap.innerHTML = open ? renderPanel() : renderFab();
|
|
98
|
+
bindEvents();
|
|
99
|
+
}
|
|
100
|
+
function renderFab() {
|
|
101
|
+
return `<button class="fw2-fab" id="fw2-fab-btn" aria-label="Open feedback">${ICON_CHAT} Feedback</button>`;
|
|
102
|
+
}
|
|
103
|
+
function renderPanel() {
|
|
104
|
+
return `
|
|
105
|
+
<div>
|
|
106
|
+
<div class="fw2-panel">
|
|
107
|
+
<div class="fw2-header">
|
|
108
|
+
<div class="fw2-header-text">
|
|
109
|
+
<h3>${title}</h3>
|
|
110
|
+
<p>${subtitle}</p>
|
|
111
|
+
</div>
|
|
112
|
+
<button class="fw2-close-x" id="fw2-close-x" aria-label="Close">×</button>
|
|
113
|
+
</div>
|
|
114
|
+
${status === 'success' ? renderSuccess() : renderForm()}
|
|
115
|
+
</div>
|
|
116
|
+
<div class="fw2-close-pill">
|
|
117
|
+
<button id="fw2-close-pill">${ICON_CLOSE} Close</button>
|
|
118
|
+
</div>
|
|
119
|
+
</div>`;
|
|
120
|
+
}
|
|
121
|
+
function renderSuccess() {
|
|
122
|
+
return `
|
|
123
|
+
<div class="fw2-success">
|
|
124
|
+
<div class="fw2-success-icon">${ICON_CHECK}</div>
|
|
125
|
+
<h4>Thank You!</h4>
|
|
126
|
+
<p>Your feedback helps us improve the<br/>${appName} experience.</p>
|
|
127
|
+
<button class="fw2-submit-another" id="fw2-another">Submit Another</button>
|
|
128
|
+
</div>`;
|
|
129
|
+
}
|
|
130
|
+
function renderForm() {
|
|
131
|
+
const stars = [1, 2, 3, 4, 5].map(s => `
|
|
132
|
+
<button class="fw2-star${s <= rating ? ' active' : ''}"
|
|
133
|
+
data-star="${s}" aria-label="${s} star${s > 1 ? 's' : ''}" aria-pressed="${s <= rating}">★</button>
|
|
134
|
+
`).join('');
|
|
135
|
+
return `
|
|
136
|
+
<div class="fw2-body">
|
|
137
|
+
<p class="fw2-question">How would you rate your experience?</p>
|
|
138
|
+
<div class="fw2-stars" role="radiogroup" aria-label="Star rating">${stars}</div>
|
|
139
|
+
<label class="fw2-textarea-label" for="fw2-message">Tell us more <span>(optional)</span></label>
|
|
140
|
+
<textarea id="fw2-message" class="fw2-textarea" placeholder="Share your thoughts...">${message}</textarea>
|
|
141
|
+
${errorMsg ? `<div class="fw2-error" role="alert">${errorMsg}</div>` : ''}
|
|
142
|
+
<button class="fw2-submit" id="fw2-submit" ${status === 'loading' ? 'disabled' : ''}>
|
|
143
|
+
${ICON_SEND} ${status === 'loading' ? 'Submitting…' : 'Submit Feedback'}
|
|
144
|
+
</button>
|
|
145
|
+
</div>`;
|
|
146
|
+
}
|
|
147
|
+
// ── Update star states ──────────────────────────────────────────────────
|
|
148
|
+
function updateStarStates() {
|
|
149
|
+
wrap.querySelectorAll('.fw2-star').forEach(btn => {
|
|
150
|
+
const starValue = Number(btn.dataset.star);
|
|
151
|
+
const isActive = starValue <= rating;
|
|
152
|
+
const isHover = starValue <= hoverRating && starValue > rating;
|
|
153
|
+
btn.classList.toggle('active', isActive);
|
|
154
|
+
btn.classList.toggle('hover', isHover);
|
|
155
|
+
btn.setAttribute('aria-pressed', String(isActive));
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// ── Event binding ────────────────────────────────────────────────────────
|
|
159
|
+
function bindEvents() {
|
|
160
|
+
var _a, _b, _c, _d, _e;
|
|
161
|
+
// FAB
|
|
162
|
+
(_a = wrap.querySelector('#fw2-fab-btn')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => { open = true; render(); });
|
|
163
|
+
// Close buttons
|
|
164
|
+
(_b = wrap.querySelector('#fw2-close-x')) === null || _b === void 0 ? void 0 : _b.addEventListener('click', () => { open = false; render(); });
|
|
165
|
+
(_c = wrap.querySelector('#fw2-close-pill')) === null || _c === void 0 ? void 0 : _c.addEventListener('click', () => { open = false; render(); });
|
|
166
|
+
// Submit another
|
|
167
|
+
(_d = wrap.querySelector('#fw2-another')) === null || _d === void 0 ? void 0 : _d.addEventListener('click', () => {
|
|
168
|
+
rating = 0;
|
|
169
|
+
hoverRating = 0;
|
|
170
|
+
message = '';
|
|
171
|
+
status = 'idle';
|
|
172
|
+
errorMsg = '';
|
|
173
|
+
render();
|
|
174
|
+
});
|
|
175
|
+
// Stars
|
|
176
|
+
wrap.querySelectorAll('.fw2-star').forEach(btn => {
|
|
177
|
+
btn.addEventListener('click', () => {
|
|
178
|
+
rating = Number(btn.dataset.star);
|
|
179
|
+
updateStarStates();
|
|
180
|
+
});
|
|
181
|
+
btn.addEventListener('mouseenter', () => {
|
|
182
|
+
hoverRating = Number(btn.dataset.star);
|
|
183
|
+
updateStarStates();
|
|
184
|
+
});
|
|
185
|
+
btn.addEventListener('mouseleave', () => {
|
|
186
|
+
hoverRating = 0;
|
|
187
|
+
updateStarStates();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
// Textarea — preserve value without full re-render
|
|
191
|
+
const ta = wrap.querySelector('#fw2-message');
|
|
192
|
+
ta === null || ta === void 0 ? void 0 : ta.addEventListener('input', () => { message = ta.value; });
|
|
193
|
+
// Submit
|
|
194
|
+
(_e = wrap.querySelector('#fw2-submit')) === null || _e === void 0 ? void 0 : _e.addEventListener('click', () => void handleSubmit());
|
|
195
|
+
// Click outside to close
|
|
196
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
197
|
+
}
|
|
198
|
+
function handleClickOutside(e) {
|
|
199
|
+
if (open && !wrap.contains(e.target)) {
|
|
200
|
+
open = false;
|
|
201
|
+
render();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// ── Submit ───────────────────────────────────────────────────────────────
|
|
205
|
+
async function handleSubmit() {
|
|
206
|
+
if (!rating) {
|
|
207
|
+
errorMsg = 'Please select a star rating before submitting.';
|
|
208
|
+
render();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
status = 'loading';
|
|
212
|
+
errorMsg = '';
|
|
213
|
+
render();
|
|
214
|
+
const payload = {
|
|
215
|
+
rating,
|
|
216
|
+
message,
|
|
217
|
+
app: appName,
|
|
218
|
+
timestamp: new Date().toISOString(),
|
|
219
|
+
url: typeof window !== 'undefined' ? window.location.href : '',
|
|
220
|
+
};
|
|
221
|
+
try {
|
|
222
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
223
|
+
if (apiKey)
|
|
224
|
+
headers['Authorization'] = `Basic ${apiKey}`;
|
|
225
|
+
const res = await fetch(apiRoute, {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
headers,
|
|
228
|
+
body: JSON.stringify(payload),
|
|
229
|
+
});
|
|
230
|
+
if (!res.ok)
|
|
231
|
+
throw new Error(`HTTP ${res.status}`);
|
|
232
|
+
status = 'success';
|
|
233
|
+
onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess(payload);
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
const error = err instanceof Error ? err : new Error('Unknown error');
|
|
237
|
+
status = 'error';
|
|
238
|
+
errorMsg = 'Something went wrong. Please try again.';
|
|
239
|
+
onError === null || onError === void 0 ? void 0 : onError(error);
|
|
240
|
+
}
|
|
241
|
+
render();
|
|
242
|
+
}
|
|
243
|
+
// ── Init ─────────────────────────────────────────────────────────────────
|
|
244
|
+
render();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export { FeedbackWidget };
|
|
248
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/FeedbackWidget.ts"],"sourcesContent":[null],"names":[],"mappings":"AAkBA,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqEX;AAED,MAAM,SAAS,GAAG,CAAA,2OAAA,CAA6O;AAC/P,MAAM,SAAS,GAAG,CAAA,iOAAA,CAAmO;AACrP,MAAM,UAAU,GAAG,CAAA,6LAAA,CAA+L;AAClN,MAAM,UAAU,GAAG,CAAA,4MAAA,CAA8M;AAE3N,SAAU,cAAc,CAAC,IAAA,GAA8B,EAAE,EAAA;IAC7D,MAAM,EACJ,QAAQ,GAAI,eAAe,EAC3B,MAAM,GAAM,EAAE,EACd,KAAK,GAAO,qBAAqB,EACjC,QAAQ,GAAI,iBAAiB,EAC7B,OAAO,GAAK,KAAK,EACjB,SAAS,EACT,OAAO,GACR,GAAG,IAAI;;IAGR,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE;QAC1C,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC;AAC7C,QAAA,KAAK,CAAC,EAAE,GAAG,YAAY;AACvB,QAAA,KAAK,CAAC,WAAW,GAAG,GAAG;AACvB,QAAA,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;IAClC;;IAGA,IAAI,IAAI,GAAU,KAAK;IACvB,IAAI,MAAM,GAAQ,CAAC;IACnB,IAAI,WAAW,GAAG,CAAC;IACnB,IAAI,OAAO,GAAO,EAAE;IACpB,IAAI,MAAM,GAA6C,MAAM;IAC7D,IAAI,QAAQ,GAAM,EAAE;;IAGpB,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC;AAC1C,IAAA,IAAI,CAAC,SAAS,GAAG,UAAU;AAC3B,IAAA,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;;AAG/B,IAAA,SAAS,MAAM,GAAA;AACb,QAAA,IAAI,CAAC,SAAS,GAAG,IAAI,GAAG,WAAW,EAAE,GAAG,SAAS,EAAE;AACnD,QAAA,UAAU,EAAE;IACd;AAEA,IAAA,SAAS,SAAS,GAAA;QAChB,OAAO,CAAA,oEAAA,EAAuE,SAAS,CAAA,kBAAA,CAAoB;IAC7G;AAEA,IAAA,SAAS,WAAW,GAAA;QAClB,OAAO;;;;;oBAKS,KAAK,CAAA;mBACN,QAAQ,CAAA;;;;YAIf,MAAM,KAAK,SAAS,GAAG,aAAa,EAAE,GAAG,UAAU,EAAE;;;wCAGzB,UAAU,CAAA;;aAErC;IACX;AAEA,IAAA,SAAS,aAAa,GAAA;QACpB,OAAO;;wCAE6B,UAAU,CAAA;;oDAEE,OAAO,CAAA;;aAE9C;IACX;AAEA,IAAA,SAAS,UAAU,GAAA;AACjB,QAAA,MAAM,KAAK,GAAG,CAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,CAAC,EAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI;+BACR,CAAC,IAAI,MAAM,GAAG,SAAS,GAAG,EAAE,CAAA;AACtC,mBAAA,EAAA,CAAC,iBAAiB,CAAC,CAAA,KAAA,EAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,EAAE,CAAA,gBAAA,EAAmB,CAAC,IAAI,MAAM,CAAA;AACzF,IAAA,CAAA,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QAEX,OAAO;;;4EAGiE,KAAK,CAAA;;+FAEc,OAAO,CAAA;UAC5F,QAAQ,GAAG,CAAA,oCAAA,EAAuC,QAAQ,CAAA,MAAA,CAAQ,GAAG,EAAE;qDAC5B,MAAM,KAAK,SAAS,GAAG,UAAU,GAAG,EAAE,CAAA;YAC/E,SAAS,CAAA,CAAA,EAAI,MAAM,KAAK,SAAS,GAAG,aAAa,GAAG,iBAAiB;;aAEpE;IACX;;AAGA,IAAA,SAAS,gBAAgB,GAAA;QACvB,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,GAAG,IAAG;YAC/C,MAAM,SAAS,GAAG,MAAM,CAAE,GAAmB,CAAC,OAAO,CAAC,IAAI,CAAC;AAC3D,YAAA,MAAM,QAAQ,GAAG,SAAS,IAAI,MAAM;YACpC,MAAM,OAAO,GAAG,SAAS,IAAI,WAAW,IAAI,SAAS,GAAG,MAAM;YAE9D,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC;YACxC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC;YACrC,GAAyB,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC3E,QAAA,CAAC,CAAC;IACJ;;AAGA,IAAA,SAAS,UAAU,GAAA;;;QAEjB,CAAA,EAAA,GAAA,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAE,gBAAgB,CAAC,OAAO,EAAE,MAAK,EAAG,IAAI,GAAG,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;;QAG/F,CAAA,EAAA,GAAA,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAE,gBAAgB,CAAC,OAAO,EAAE,MAAK,EAAG,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;QAChG,CAAA,EAAA,GAAA,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAC,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAE,gBAAgB,CAAC,OAAO,EAAE,MAAK,EAAG,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;;AAGnG,QAAA,CAAA,EAAA,GAAA,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAE,gBAAgB,CAAC,OAAO,EAAE,MAAK;YACjE,MAAM,GAAG,CAAC;YAAE,WAAW,GAAG,CAAC;YAAE,OAAO,GAAG,EAAE;YAAE,MAAM,GAAG,MAAM;YAAE,QAAQ,GAAG,EAAE;AACzE,YAAA,MAAM,EAAE;AACV,QAAA,CAAC,CAAC;;QAGF,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,GAAG,IAAG;AAC/C,YAAA,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAK;gBACjC,MAAM,GAAG,MAAM,CAAE,GAAmB,CAAC,OAAO,CAAC,IAAI,CAAC;AAClD,gBAAA,gBAAgB,EAAE;AACpB,YAAA,CAAC,CAAC;AACF,YAAA,GAAG,CAAC,gBAAgB,CAAC,YAAY,EAAE,MAAK;gBACtC,WAAW,GAAG,MAAM,CAAE,GAAmB,CAAC,OAAO,CAAC,IAAI,CAAC;AACvD,gBAAA,gBAAgB,EAAE;AACpB,YAAA,CAAC,CAAC;AACF,YAAA,GAAG,CAAC,gBAAgB,CAAC,YAAY,EAAE,MAAK;gBACtC,WAAW,GAAG,CAAC;AACf,gBAAA,gBAAgB,EAAE;AACpB,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;;QAGF,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,cAAc,CAAwB;QACpE,EAAE,KAAA,IAAA,IAAF,EAAE,KAAA,MAAA,GAAA,MAAA,GAAF,EAAE,CAAE,gBAAgB,CAAC,OAAO,EAAE,MAAK,EAAG,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;;AAG5D,QAAA,CAAA,EAAA,GAAA,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,0CAAE,gBAAgB,CAAC,OAAO,EAAE,MAAM,KAAK,YAAY,EAAE,CAAC;;AAGvF,QAAA,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,kBAAkB,CAAC;IAC5D;IAEA,SAAS,kBAAkB,CAAC,CAAa,EAAA;AACvC,QAAA,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAc,CAAC,EAAE;YAC5C,IAAI,GAAG,KAAK;AACZ,YAAA,MAAM,EAAE;QACV;IACF;;AAGA,IAAA,eAAe,YAAY,GAAA;QACzB,IAAI,CAAC,MAAM,EAAE;YACX,QAAQ,GAAG,gDAAgD;AAC3D,YAAA,MAAM,EAAE;YACR;QACF;QAEA,MAAM,GAAK,SAAS;QACpB,QAAQ,GAAG,EAAE;AACb,QAAA,MAAM,EAAE;AAER,QAAA,MAAM,OAAO,GAAoB;YAC/B,MAAM;YACN,OAAO;AACP,YAAA,GAAG,EAAQ,OAAO;AAClB,YAAA,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;AACnC,YAAA,GAAG,EAAQ,OAAO,MAAM,KAAK,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,EAAE;SACrE;AAED,QAAA,IAAI;AACF,YAAA,MAAM,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE;AAC9E,YAAA,IAAI,MAAM;AAAE,gBAAA,OAAO,CAAC,eAAe,CAAC,GAAG,CAAA,MAAA,EAAS,MAAM,EAAE;AAExD,YAAA,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;AAChC,gBAAA,MAAM,EAAE,MAAM;gBACd,OAAO;AACP,gBAAA,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;AAC9B,aAAA,CAAC;YAEF,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,CAAA,KAAA,EAAQ,GAAG,CAAC,MAAM,CAAA,CAAE,CAAC;YAElD,MAAM,GAAG,SAAS;AAClB,YAAA,SAAS,aAAT,SAAS,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAT,SAAS,CAAG,OAAO,CAAC;QACtB;QAAE,OAAO,GAAG,EAAE;AACZ,YAAA,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,GAAG,GAAG,GAAG,IAAI,KAAK,CAAC,eAAe,CAAC;YACrE,MAAM,GAAK,OAAO;YAClB,QAAQ,GAAG,yCAAyC;AACpD,YAAA,OAAO,aAAP,OAAO,KAAA,MAAA,GAAA,MAAA,GAAP,OAAO,CAAG,KAAK,CAAC;QAClB;AAEA,QAAA,MAAM,EAAE;IACV;;AAGA,IAAA,MAAM,EAAE;AACV;;;;"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.LMSFeedbackWidget = {}));
|
|
5
|
+
})(this, (function (exports) { 'use strict';
|
|
6
|
+
|
|
7
|
+
const CSS = `
|
|
8
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
|
|
9
|
+
.fw2-wrap { position:fixed; bottom:24px; right:24px; z-index:9999; font-family:'Inter',sans-serif; }
|
|
10
|
+
.fw2-fab {
|
|
11
|
+
display:flex; align-items:center; gap:8px;
|
|
12
|
+
background:#2c3e6b; color:#fff; border:none; cursor:pointer;
|
|
13
|
+
border-radius:24px; padding:12px 20px; font-size:14px; font-weight:500;
|
|
14
|
+
box-shadow:0 4px 16px rgba(0,0,0,0.25); transition:background .2s,transform .15s;
|
|
15
|
+
font-family:'Inter',sans-serif;
|
|
16
|
+
}
|
|
17
|
+
.fw2-fab:hover { background:#1e2e55; transform:translateY(-1px); }
|
|
18
|
+
.fw2-panel {
|
|
19
|
+
background:#fff; border-radius:12px; box-shadow:0 8px 40px rgba(0,0,0,0.18);
|
|
20
|
+
width:320px; overflow:hidden; margin-bottom:12px;
|
|
21
|
+
animation:fw2-pop .2s cubic-bezier(.34,1.4,.64,1) both;
|
|
22
|
+
}
|
|
23
|
+
@keyframes fw2-pop { from{opacity:0;transform:scale(.9) translateY(10px)} to{opacity:1;transform:scale(1) translateY(0)} }
|
|
24
|
+
.fw2-header {
|
|
25
|
+
background:#2c3e6b; color:#fff; padding:16px 18px 14px;
|
|
26
|
+
display:flex; align-items:flex-start; justify-content:space-between;
|
|
27
|
+
}
|
|
28
|
+
.fw2-header-text h3 { font-size:15px; font-weight:600; margin:0 0 3px; line-height:1.3; color:#fff; }
|
|
29
|
+
.fw2-header-text p { font-size:12px; opacity:.75; margin:0; }
|
|
30
|
+
.fw2-close-x { background:none; border:none; color:#fff; cursor:pointer; font-size:22px; line-height:1; opacity:.8; padding:0; margin-left:8px; }
|
|
31
|
+
.fw2-close-x:hover { opacity:1; }
|
|
32
|
+
.fw2-body { padding:20px 18px 18px; }
|
|
33
|
+
.fw2-question { font-size:13.5px; color:#333; text-align:center; margin-bottom:14px; }
|
|
34
|
+
.fw2-stars { display:flex; justify-content:center; gap:8px; margin-bottom:18px; }
|
|
35
|
+
.fw2-star {
|
|
36
|
+
background:none; border:none; padding:0; cursor:pointer; font-size:28px; line-height:1;
|
|
37
|
+
color:transparent; -webkit-text-stroke:1.5px #aaa; transition:-webkit-text-stroke .1s,color .1s,transform .1s;
|
|
38
|
+
}
|
|
39
|
+
.fw2-star:hover,.fw2-star.hover { -webkit-text-stroke:1.5px #c9a227; transform:scale(1.15); }
|
|
40
|
+
.fw2-star.active { -webkit-text-stroke:1.5px #c9a227; color:#c9a227; }
|
|
41
|
+
.fw2-textarea-label { font-size:13px; color:#444; margin-bottom:6px; display:block; }
|
|
42
|
+
.fw2-textarea-label span { color:#888; font-size:12px; }
|
|
43
|
+
.fw2-textarea {
|
|
44
|
+
width:100%; border:1px solid #d0d5dd; border-radius:6px; padding:10px 12px;
|
|
45
|
+
font-family:'Inter',sans-serif; font-size:13px; color:#333; resize:vertical; min-height:88px;
|
|
46
|
+
outline:none; transition:border-color .2s; background:#fff; margin-bottom:14px; box-sizing:border-box;
|
|
47
|
+
}
|
|
48
|
+
.fw2-textarea:focus { border-color:#2c3e6b; }
|
|
49
|
+
.fw2-submit {
|
|
50
|
+
width:100%; background:#6b7fa8; color:#fff; border:none; border-radius:6px; padding:11px;
|
|
51
|
+
font-family:'Inter',sans-serif; font-size:14px; font-weight:500; cursor:pointer;
|
|
52
|
+
display:flex; align-items:center; justify-content:center; gap:8px; transition:background .2s;
|
|
53
|
+
}
|
|
54
|
+
.fw2-submit:hover:not(:disabled) { background:#2c3e6b; }
|
|
55
|
+
.fw2-submit:disabled { opacity:.6; cursor:not-allowed; }
|
|
56
|
+
.fw2-error { font-size:12px; color:#dc2626; margin-bottom:10px; text-align:center; }
|
|
57
|
+
.fw2-success { padding:28px 18px 22px; text-align:center; }
|
|
58
|
+
.fw2-success-icon {
|
|
59
|
+
width:60px; height:60px; border-radius:50%; border:3px solid #c9a227;
|
|
60
|
+
display:flex; align-items:center; justify-content:center; margin:0 auto 16px; color:#c9a227;
|
|
61
|
+
}
|
|
62
|
+
.fw2-success h4 { font-size:18px; font-weight:600; color:#222; margin:0 0 8px; }
|
|
63
|
+
.fw2-success p { font-size:13px; color:#666; margin:0 0 20px; line-height:1.5; }
|
|
64
|
+
.fw2-submit-another {
|
|
65
|
+
background:#fff; border:1.5px solid #ccc; border-radius:6px; padding:9px 20px;
|
|
66
|
+
font-family:'Inter',sans-serif; font-size:13px; font-weight:500; color:#444; cursor:pointer;
|
|
67
|
+
}
|
|
68
|
+
.fw2-submit-another:hover { border-color:#2c3e6b; color:#2c3e6b; }
|
|
69
|
+
.fw2-close-pill { display:flex; align-items:center; justify-content:flex-end; margin-top:8px; }
|
|
70
|
+
.fw2-close-pill button {
|
|
71
|
+
background:#fff; border:1.5px solid #d0d5dd; border-radius:20px; padding:6px 16px;
|
|
72
|
+
font-family:'Inter',sans-serif; font-size:13px; color:#555; cursor:pointer;
|
|
73
|
+
display:flex; align-items:center; gap:6px; box-shadow:0 2px 8px rgba(0,0,0,0.08);
|
|
74
|
+
}
|
|
75
|
+
.fw2-close-pill button:hover { border-color:#999; }
|
|
76
|
+
`;
|
|
77
|
+
const ICON_SEND = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>`;
|
|
78
|
+
const ICON_CHAT = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>`;
|
|
79
|
+
const ICON_CHECK = `<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
|
|
80
|
+
const ICON_CLOSE = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
|
81
|
+
function FeedbackWidget(opts = {}) {
|
|
82
|
+
const { apiRoute = '/api/feedback', apiKey = '', title = 'Share Your Feedback', subtitle = 'Help us improve', appName = 'App', onSuccess, onError, } = opts;
|
|
83
|
+
// ── Inject styles once ──────────────────────────────────────────────────
|
|
84
|
+
if (!document.getElementById('fw2-styles')) {
|
|
85
|
+
const style = document.createElement('style');
|
|
86
|
+
style.id = 'fw2-styles';
|
|
87
|
+
style.textContent = CSS;
|
|
88
|
+
document.head.appendChild(style);
|
|
89
|
+
}
|
|
90
|
+
// ── State ───────────────────────────────────────────────────────────────
|
|
91
|
+
let open = false;
|
|
92
|
+
let rating = 0;
|
|
93
|
+
let hoverRating = 0;
|
|
94
|
+
let message = '';
|
|
95
|
+
let status = 'idle';
|
|
96
|
+
let errorMsg = '';
|
|
97
|
+
// ── Root element ────────────────────────────────────────────────────────
|
|
98
|
+
const wrap = document.createElement('div');
|
|
99
|
+
wrap.className = 'fw2-wrap';
|
|
100
|
+
document.body.appendChild(wrap);
|
|
101
|
+
// ── Render ───────────────────────────────────────────────────────────────
|
|
102
|
+
function render() {
|
|
103
|
+
wrap.innerHTML = open ? renderPanel() : renderFab();
|
|
104
|
+
bindEvents();
|
|
105
|
+
}
|
|
106
|
+
function renderFab() {
|
|
107
|
+
return `<button class="fw2-fab" id="fw2-fab-btn" aria-label="Open feedback">${ICON_CHAT} Feedback</button>`;
|
|
108
|
+
}
|
|
109
|
+
function renderPanel() {
|
|
110
|
+
return `
|
|
111
|
+
<div>
|
|
112
|
+
<div class="fw2-panel">
|
|
113
|
+
<div class="fw2-header">
|
|
114
|
+
<div class="fw2-header-text">
|
|
115
|
+
<h3>${title}</h3>
|
|
116
|
+
<p>${subtitle}</p>
|
|
117
|
+
</div>
|
|
118
|
+
<button class="fw2-close-x" id="fw2-close-x" aria-label="Close">×</button>
|
|
119
|
+
</div>
|
|
120
|
+
${status === 'success' ? renderSuccess() : renderForm()}
|
|
121
|
+
</div>
|
|
122
|
+
<div class="fw2-close-pill">
|
|
123
|
+
<button id="fw2-close-pill">${ICON_CLOSE} Close</button>
|
|
124
|
+
</div>
|
|
125
|
+
</div>`;
|
|
126
|
+
}
|
|
127
|
+
function renderSuccess() {
|
|
128
|
+
return `
|
|
129
|
+
<div class="fw2-success">
|
|
130
|
+
<div class="fw2-success-icon">${ICON_CHECK}</div>
|
|
131
|
+
<h4>Thank You!</h4>
|
|
132
|
+
<p>Your feedback helps us improve the<br/>${appName} experience.</p>
|
|
133
|
+
<button class="fw2-submit-another" id="fw2-another">Submit Another</button>
|
|
134
|
+
</div>`;
|
|
135
|
+
}
|
|
136
|
+
function renderForm() {
|
|
137
|
+
const stars = [1, 2, 3, 4, 5].map(s => `
|
|
138
|
+
<button class="fw2-star${s <= rating ? ' active' : ''}"
|
|
139
|
+
data-star="${s}" aria-label="${s} star${s > 1 ? 's' : ''}" aria-pressed="${s <= rating}">★</button>
|
|
140
|
+
`).join('');
|
|
141
|
+
return `
|
|
142
|
+
<div class="fw2-body">
|
|
143
|
+
<p class="fw2-question">How would you rate your experience?</p>
|
|
144
|
+
<div class="fw2-stars" role="radiogroup" aria-label="Star rating">${stars}</div>
|
|
145
|
+
<label class="fw2-textarea-label" for="fw2-message">Tell us more <span>(optional)</span></label>
|
|
146
|
+
<textarea id="fw2-message" class="fw2-textarea" placeholder="Share your thoughts...">${message}</textarea>
|
|
147
|
+
${errorMsg ? `<div class="fw2-error" role="alert">${errorMsg}</div>` : ''}
|
|
148
|
+
<button class="fw2-submit" id="fw2-submit" ${status === 'loading' ? 'disabled' : ''}>
|
|
149
|
+
${ICON_SEND} ${status === 'loading' ? 'Submitting…' : 'Submit Feedback'}
|
|
150
|
+
</button>
|
|
151
|
+
</div>`;
|
|
152
|
+
}
|
|
153
|
+
// ── Update star states ──────────────────────────────────────────────────
|
|
154
|
+
function updateStarStates() {
|
|
155
|
+
wrap.querySelectorAll('.fw2-star').forEach(btn => {
|
|
156
|
+
const starValue = Number(btn.dataset.star);
|
|
157
|
+
const isActive = starValue <= rating;
|
|
158
|
+
const isHover = starValue <= hoverRating && starValue > rating;
|
|
159
|
+
btn.classList.toggle('active', isActive);
|
|
160
|
+
btn.classList.toggle('hover', isHover);
|
|
161
|
+
btn.setAttribute('aria-pressed', String(isActive));
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
// ── Event binding ────────────────────────────────────────────────────────
|
|
165
|
+
function bindEvents() {
|
|
166
|
+
var _a, _b, _c, _d, _e;
|
|
167
|
+
// FAB
|
|
168
|
+
(_a = wrap.querySelector('#fw2-fab-btn')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => { open = true; render(); });
|
|
169
|
+
// Close buttons
|
|
170
|
+
(_b = wrap.querySelector('#fw2-close-x')) === null || _b === void 0 ? void 0 : _b.addEventListener('click', () => { open = false; render(); });
|
|
171
|
+
(_c = wrap.querySelector('#fw2-close-pill')) === null || _c === void 0 ? void 0 : _c.addEventListener('click', () => { open = false; render(); });
|
|
172
|
+
// Submit another
|
|
173
|
+
(_d = wrap.querySelector('#fw2-another')) === null || _d === void 0 ? void 0 : _d.addEventListener('click', () => {
|
|
174
|
+
rating = 0;
|
|
175
|
+
hoverRating = 0;
|
|
176
|
+
message = '';
|
|
177
|
+
status = 'idle';
|
|
178
|
+
errorMsg = '';
|
|
179
|
+
render();
|
|
180
|
+
});
|
|
181
|
+
// Stars
|
|
182
|
+
wrap.querySelectorAll('.fw2-star').forEach(btn => {
|
|
183
|
+
btn.addEventListener('click', () => {
|
|
184
|
+
rating = Number(btn.dataset.star);
|
|
185
|
+
updateStarStates();
|
|
186
|
+
});
|
|
187
|
+
btn.addEventListener('mouseenter', () => {
|
|
188
|
+
hoverRating = Number(btn.dataset.star);
|
|
189
|
+
updateStarStates();
|
|
190
|
+
});
|
|
191
|
+
btn.addEventListener('mouseleave', () => {
|
|
192
|
+
hoverRating = 0;
|
|
193
|
+
updateStarStates();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
// Textarea — preserve value without full re-render
|
|
197
|
+
const ta = wrap.querySelector('#fw2-message');
|
|
198
|
+
ta === null || ta === void 0 ? void 0 : ta.addEventListener('input', () => { message = ta.value; });
|
|
199
|
+
// Submit
|
|
200
|
+
(_e = wrap.querySelector('#fw2-submit')) === null || _e === void 0 ? void 0 : _e.addEventListener('click', () => void handleSubmit());
|
|
201
|
+
// Click outside to close
|
|
202
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
203
|
+
}
|
|
204
|
+
function handleClickOutside(e) {
|
|
205
|
+
if (open && !wrap.contains(e.target)) {
|
|
206
|
+
open = false;
|
|
207
|
+
render();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// ── Submit ───────────────────────────────────────────────────────────────
|
|
211
|
+
async function handleSubmit() {
|
|
212
|
+
if (!rating) {
|
|
213
|
+
errorMsg = 'Please select a star rating before submitting.';
|
|
214
|
+
render();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
status = 'loading';
|
|
218
|
+
errorMsg = '';
|
|
219
|
+
render();
|
|
220
|
+
const payload = {
|
|
221
|
+
rating,
|
|
222
|
+
message,
|
|
223
|
+
app: appName,
|
|
224
|
+
timestamp: new Date().toISOString(),
|
|
225
|
+
url: typeof window !== 'undefined' ? window.location.href : '',
|
|
226
|
+
};
|
|
227
|
+
try {
|
|
228
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
229
|
+
if (apiKey)
|
|
230
|
+
headers['Authorization'] = `Basic ${apiKey}`;
|
|
231
|
+
const res = await fetch(apiRoute, {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers,
|
|
234
|
+
body: JSON.stringify(payload),
|
|
235
|
+
});
|
|
236
|
+
if (!res.ok)
|
|
237
|
+
throw new Error(`HTTP ${res.status}`);
|
|
238
|
+
status = 'success';
|
|
239
|
+
onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess(payload);
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
const error = err instanceof Error ? err : new Error('Unknown error');
|
|
243
|
+
status = 'error';
|
|
244
|
+
errorMsg = 'Something went wrong. Please try again.';
|
|
245
|
+
onError === null || onError === void 0 ? void 0 : onError(error);
|
|
246
|
+
}
|
|
247
|
+
render();
|
|
248
|
+
}
|
|
249
|
+
// ── Init ─────────────────────────────────────────────────────────────────
|
|
250
|
+
render();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
exports.FeedbackWidget = FeedbackWidget;
|
|
254
|
+
|
|
255
|
+
}));
|
|
256
|
+
//# sourceMappingURL=index.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.umd.js","sources":["../src/FeedbackWidget.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@villetorio/lms-feedback-widget",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Floating feedback widget — framework agnostic, works in Svelte, SvelteKit, Sapper, React, Vue, plain HTML",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"private": false,
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"registry": "https://registry.npmjs.org/",
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"require": "./dist/index.cjs",
|
|
19
|
+
"default": "./dist/index.umd.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": ["dist"],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "rollup -c rollup.config.js",
|
|
25
|
+
"dev": "rollup -c rollup.config.js --watch",
|
|
26
|
+
"type-check": "tsc --noEmit",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
31
|
+
"@rollup/plugin-typescript": "^11.0.0",
|
|
32
|
+
"rollup": "^4.0.0",
|
|
33
|
+
"tslib": "^2.6.0",
|
|
34
|
+
"typescript": "^5.0.0"
|
|
35
|
+
},
|
|
36
|
+
"keywords": ["svelte", "react","sapper", "feedback", "widget", "vanilla"]
|
|
37
|
+
}
|