@product7/feedback-sdk 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/feedback-sdk.js +327 -278
- package/dist/feedback-sdk.js.map +1 -1
- package/dist/feedback-sdk.min.js +1 -1
- package/dist/feedback-sdk.min.js.map +1 -1
- package/package.json +1 -1
- package/src/core/FeedbackSDK.js +1 -1
- package/src/docs/api.md +104 -598
- package/src/docs/example.md +317 -681
- package/src/docs/installation.md +195 -187
- package/src/styles/styles.js +202 -201
- package/src/widgets/BaseWidget.js +127 -79
- package/src/widgets/ButtonWidget.js +2 -2
|
@@ -11,12 +11,14 @@ export class BaseWidget {
|
|
|
11
11
|
theme: this.sdk.config.theme,
|
|
12
12
|
boardId: this.sdk.config.boardId,
|
|
13
13
|
autoShow: false,
|
|
14
|
+
showBackdrop: true,
|
|
14
15
|
customStyles: {},
|
|
15
16
|
...options,
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
this.element = null;
|
|
19
|
-
this.
|
|
20
|
+
this.panelElement = null;
|
|
21
|
+
this.backdropElement = null;
|
|
20
22
|
this.mounted = false;
|
|
21
23
|
this.destroyed = false;
|
|
22
24
|
|
|
@@ -74,23 +76,47 @@ export class BaseWidget {
|
|
|
74
76
|
return this;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
openPanel() {
|
|
78
80
|
this.state.isOpen = true;
|
|
79
|
-
this.
|
|
81
|
+
this._renderPanel();
|
|
82
|
+
|
|
83
|
+
requestAnimationFrame(() => {
|
|
84
|
+
if (this.panelElement) {
|
|
85
|
+
this.panelElement.classList.add('open');
|
|
86
|
+
}
|
|
87
|
+
if (this.backdropElement) {
|
|
88
|
+
this.backdropElement.classList.add('show');
|
|
89
|
+
}
|
|
90
|
+
});
|
|
80
91
|
}
|
|
81
92
|
|
|
82
|
-
|
|
83
|
-
this.
|
|
84
|
-
|
|
85
|
-
this.modalElement.remove();
|
|
86
|
-
this.modalElement = null;
|
|
93
|
+
closePanel() {
|
|
94
|
+
if (this.panelElement) {
|
|
95
|
+
this.panelElement.classList.remove('open');
|
|
87
96
|
}
|
|
88
|
-
this.
|
|
97
|
+
if (this.backdropElement) {
|
|
98
|
+
this.backdropElement.classList.remove('show');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
this.state.isOpen = false;
|
|
103
|
+
if (this.panelElement && this.panelElement.parentNode) {
|
|
104
|
+
this.panelElement.parentNode.removeChild(this.panelElement);
|
|
105
|
+
this.panelElement = null;
|
|
106
|
+
}
|
|
107
|
+
if (this.backdropElement && this.backdropElement.parentNode) {
|
|
108
|
+
this.backdropElement.parentNode.removeChild(this.backdropElement);
|
|
109
|
+
this.backdropElement = null;
|
|
110
|
+
}
|
|
111
|
+
this._resetForm();
|
|
112
|
+
}, 300);
|
|
89
113
|
}
|
|
90
114
|
|
|
91
115
|
async submitFeedback() {
|
|
92
116
|
if (this.state.isSubmitting) return;
|
|
93
117
|
|
|
118
|
+
this._hideError();
|
|
119
|
+
|
|
94
120
|
try {
|
|
95
121
|
this.state.isSubmitting = true;
|
|
96
122
|
this._updateSubmitButton();
|
|
@@ -111,7 +137,7 @@ export class BaseWidget {
|
|
|
111
137
|
const response = await this.apiService.submitFeedback(payload);
|
|
112
138
|
|
|
113
139
|
this._showSuccessMessage();
|
|
114
|
-
this.
|
|
140
|
+
this.closePanel();
|
|
115
141
|
|
|
116
142
|
this.sdk.eventBus.emit('feedback:submitted', {
|
|
117
143
|
widget: this,
|
|
@@ -137,7 +163,7 @@ export class BaseWidget {
|
|
|
137
163
|
if (this.destroyed) return;
|
|
138
164
|
|
|
139
165
|
this.onDestroy();
|
|
140
|
-
this.
|
|
166
|
+
this.closePanel();
|
|
141
167
|
|
|
142
168
|
if (this.element && this.element.parentNode) {
|
|
143
169
|
this.element.parentNode.removeChild(this.element);
|
|
@@ -160,38 +186,46 @@ export class BaseWidget {
|
|
|
160
186
|
}
|
|
161
187
|
|
|
162
188
|
_bindMethods() {
|
|
163
|
-
this.
|
|
164
|
-
this.
|
|
189
|
+
this.openPanel = this.openPanel.bind(this);
|
|
190
|
+
this.closePanel = this.closePanel.bind(this);
|
|
165
191
|
this.submitFeedback = this.submitFeedback.bind(this);
|
|
166
192
|
}
|
|
167
193
|
|
|
168
|
-
|
|
169
|
-
if (this.
|
|
194
|
+
_renderPanel() {
|
|
195
|
+
if (this.panelElement) return;
|
|
196
|
+
|
|
197
|
+
if (this.options.showBackdrop) {
|
|
198
|
+
this.backdropElement = document.createElement('div');
|
|
199
|
+
this.backdropElement.className = 'feedback-panel-backdrop';
|
|
200
|
+
document.body.appendChild(this.backdropElement);
|
|
201
|
+
|
|
202
|
+
this.backdropElement.addEventListener('click', this.closePanel);
|
|
203
|
+
}
|
|
170
204
|
|
|
171
|
-
this.
|
|
172
|
-
this.
|
|
173
|
-
this.
|
|
205
|
+
this.panelElement = document.createElement('div');
|
|
206
|
+
this.panelElement.className = `feedback-panel theme-${this.options.theme}`;
|
|
207
|
+
this.panelElement.innerHTML = this._getPanelHTML();
|
|
174
208
|
|
|
175
|
-
document.body.appendChild(this.
|
|
176
|
-
this.
|
|
209
|
+
document.body.appendChild(this.panelElement);
|
|
210
|
+
this._attachPanelEvents();
|
|
177
211
|
|
|
178
|
-
const firstInput = this.
|
|
212
|
+
const firstInput = this.panelElement.querySelector('input, textarea');
|
|
179
213
|
if (firstInput) {
|
|
180
|
-
setTimeout(() => firstInput.focus(),
|
|
214
|
+
setTimeout(() => firstInput.focus(), 350);
|
|
181
215
|
}
|
|
182
216
|
}
|
|
183
217
|
|
|
184
|
-
|
|
218
|
+
_getPanelHTML() {
|
|
185
219
|
return `
|
|
186
|
-
<div class="feedback-
|
|
187
|
-
<div class="feedback-
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
220
|
+
<div class="feedback-panel-content">
|
|
221
|
+
<div class="feedback-panel-header">
|
|
222
|
+
<h3>Send Feedback</h3>
|
|
223
|
+
<button class="feedback-panel-close" type="button" aria-label="Close">×</button>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="feedback-panel-body">
|
|
192
226
|
<form class="feedback-form">
|
|
193
227
|
<div class="feedback-form-group">
|
|
194
|
-
<label for="feedback-title-${this.id}">Title</label>
|
|
228
|
+
<label for="feedback-title-${this.id}">Title (optional)</label>
|
|
195
229
|
<input
|
|
196
230
|
type="text"
|
|
197
231
|
id="feedback-title-${this.id}"
|
|
@@ -205,62 +239,59 @@ export class BaseWidget {
|
|
|
205
239
|
<textarea
|
|
206
240
|
id="feedback-content-${this.id}"
|
|
207
241
|
name="content"
|
|
208
|
-
placeholder="Tell us
|
|
242
|
+
placeholder="Tell us what you think..."
|
|
209
243
|
required
|
|
210
244
|
>${this.state.content}</textarea>
|
|
211
245
|
</div>
|
|
246
|
+
<div class="feedback-error" role="alert"></div>
|
|
212
247
|
<div class="feedback-form-actions">
|
|
213
|
-
<button type="button" class="feedback-btn feedback-btn-cancel">Cancel</button>
|
|
214
248
|
<button type="submit" class="feedback-btn feedback-btn-submit">
|
|
215
249
|
${this.state.isSubmitting ? 'Sending...' : 'Send Feedback'}
|
|
216
250
|
</button>
|
|
217
251
|
</div>
|
|
218
|
-
<div class="feedback-error" style="display: none;"></div>
|
|
219
252
|
</form>
|
|
220
253
|
</div>
|
|
221
254
|
</div>
|
|
222
255
|
`;
|
|
223
256
|
}
|
|
224
257
|
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
modal
|
|
229
|
-
.querySelector('.feedback-modal-close')
|
|
230
|
-
.addEventListener('click', this.closeModal);
|
|
231
|
-
modal
|
|
232
|
-
.querySelector('.feedback-btn-cancel')
|
|
233
|
-
.addEventListener('click', this.closeModal);
|
|
234
|
-
modal
|
|
235
|
-
.querySelector('.feedback-modal-overlay')
|
|
236
|
-
.addEventListener('click', (e) => {
|
|
237
|
-
if (e.target === e.currentTarget) {
|
|
238
|
-
this.closeModal();
|
|
239
|
-
}
|
|
240
|
-
});
|
|
258
|
+
_attachPanelEvents() {
|
|
259
|
+
const panel = this.panelElement;
|
|
241
260
|
|
|
242
|
-
|
|
261
|
+
panel
|
|
262
|
+
.querySelector('.feedback-panel-close')
|
|
263
|
+
.addEventListener('click', this.closePanel);
|
|
264
|
+
|
|
265
|
+
const form = panel.querySelector('.feedback-form');
|
|
243
266
|
form.addEventListener('submit', (e) => {
|
|
244
267
|
e.preventDefault();
|
|
245
268
|
this.submitFeedback();
|
|
246
269
|
});
|
|
247
270
|
|
|
248
|
-
|
|
271
|
+
panel
|
|
249
272
|
.querySelector('input[name="title"]')
|
|
250
273
|
.addEventListener('input', (e) => {
|
|
251
274
|
this.state.title = e.target.value;
|
|
252
275
|
});
|
|
253
276
|
|
|
254
|
-
|
|
277
|
+
panel
|
|
255
278
|
.querySelector('textarea[name="content"]')
|
|
256
279
|
.addEventListener('input', (e) => {
|
|
257
280
|
this.state.content = e.target.value;
|
|
258
281
|
});
|
|
282
|
+
|
|
283
|
+
const handleEscape = (e) => {
|
|
284
|
+
if (e.key === 'Escape') {
|
|
285
|
+
this.closePanel();
|
|
286
|
+
document.removeEventListener('keydown', handleEscape);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
document.addEventListener('keydown', handleEscape);
|
|
259
290
|
}
|
|
260
291
|
|
|
261
292
|
_updateSubmitButton() {
|
|
262
|
-
if (this.
|
|
263
|
-
const submitBtn = this.
|
|
293
|
+
if (this.panelElement) {
|
|
294
|
+
const submitBtn = this.panelElement.querySelector('.feedback-btn-submit');
|
|
264
295
|
if (submitBtn) {
|
|
265
296
|
submitBtn.textContent = this.state.isSubmitting
|
|
266
297
|
? 'Sending...'
|
|
@@ -271,15 +302,21 @@ export class BaseWidget {
|
|
|
271
302
|
}
|
|
272
303
|
|
|
273
304
|
_showError(message) {
|
|
274
|
-
if (this.
|
|
275
|
-
const errorElement = this.
|
|
276
|
-
errorElement
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
305
|
+
if (this.panelElement) {
|
|
306
|
+
const errorElement = this.panelElement.querySelector('.feedback-error');
|
|
307
|
+
if (errorElement) {
|
|
308
|
+
errorElement.textContent = message;
|
|
309
|
+
errorElement.classList.add('show');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_hideError() {
|
|
315
|
+
if (this.panelElement) {
|
|
316
|
+
const errorElement = this.panelElement.querySelector('.feedback-error');
|
|
317
|
+
if (errorElement) {
|
|
318
|
+
errorElement.classList.remove('show');
|
|
319
|
+
}
|
|
283
320
|
}
|
|
284
321
|
}
|
|
285
322
|
|
|
@@ -288,26 +325,29 @@ export class BaseWidget {
|
|
|
288
325
|
notification.className = 'feedback-success-notification';
|
|
289
326
|
notification.innerHTML = `
|
|
290
327
|
<div class="feedback-success-content">
|
|
291
|
-
<
|
|
292
|
-
<
|
|
328
|
+
<div class="feedback-success-icon">✓</div>
|
|
329
|
+
<span>Feedback submitted successfully!</span>
|
|
330
|
+
<button class="feedback-success-close" aria-label="Close">×</button>
|
|
293
331
|
</div>
|
|
294
332
|
`;
|
|
295
333
|
|
|
296
334
|
document.body.appendChild(notification);
|
|
297
335
|
|
|
298
|
-
|
|
336
|
+
const closeBtn = notification.querySelector('.feedback-success-close');
|
|
337
|
+
const closeNotification = () => {
|
|
299
338
|
if (notification.parentNode) {
|
|
300
|
-
notification.
|
|
339
|
+
notification.style.opacity = '0';
|
|
340
|
+
setTimeout(() => {
|
|
341
|
+
if (notification.parentNode) {
|
|
342
|
+
notification.parentNode.removeChild(notification);
|
|
343
|
+
}
|
|
344
|
+
}, 300);
|
|
301
345
|
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (notification.parentNode) {
|
|
308
|
-
notification.parentNode.removeChild(notification);
|
|
309
|
-
}
|
|
310
|
-
});
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
closeBtn.addEventListener('click', closeNotification);
|
|
349
|
+
|
|
350
|
+
setTimeout(closeNotification, 4000);
|
|
311
351
|
}
|
|
312
352
|
|
|
313
353
|
_resetForm() {
|
|
@@ -324,11 +364,19 @@ export class BaseWidget {
|
|
|
324
364
|
`theme-${this.options.theme}`
|
|
325
365
|
);
|
|
326
366
|
}
|
|
327
|
-
if (this.
|
|
328
|
-
this.
|
|
367
|
+
if (this.panelElement) {
|
|
368
|
+
this.panelElement.className = this.panelElement.className.replace(
|
|
329
369
|
/theme-\w+/,
|
|
330
370
|
`theme-${this.options.theme}`
|
|
331
371
|
);
|
|
332
372
|
}
|
|
333
373
|
}
|
|
334
|
-
|
|
374
|
+
|
|
375
|
+
openModal() {
|
|
376
|
+
this.openPanel();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
closeModal() {
|
|
380
|
+
this.closePanel();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
@@ -27,7 +27,7 @@ export class ButtonWidget extends BaseWidget {
|
|
|
27
27
|
|
|
28
28
|
_attachEvents() {
|
|
29
29
|
const button = this.element.querySelector('.feedback-trigger-btn');
|
|
30
|
-
button.addEventListener('click', this.
|
|
30
|
+
button.addEventListener('click', this.openPanel);
|
|
31
31
|
|
|
32
32
|
button.addEventListener('mouseenter', () => {
|
|
33
33
|
if (!this.state.isSubmitting) {
|
|
@@ -59,4 +59,4 @@ export class ButtonWidget extends BaseWidget {
|
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
}
|
|
62
|
+
}
|