wakz-chat-widget 1.0.1 → 2.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/index.js +419 -245
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WAKZ Chat Widget
|
|
2
|
+
* WAKZ Chat Widget v3.0.0
|
|
3
3
|
* ─────────────────────────────────────────────────────────────────
|
|
4
4
|
* A production-grade, self-contained chat widget using Shadow DOM.
|
|
5
|
-
*
|
|
5
|
+
* Premium glassmorphism design matching the WAKZ site design system.
|
|
6
6
|
*
|
|
7
7
|
* Embed: <script src="/wakz-widget.js" data-api-key="xxx" data-server="https://..." async></script>
|
|
8
|
+
* New attrs: data-icon-color="#171717" data-icon-shape="circle"
|
|
8
9
|
*
|
|
9
10
|
* ZERO external dependencies — pure vanilla JavaScript.
|
|
10
11
|
*/
|
|
@@ -50,7 +51,9 @@
|
|
|
50
51
|
if (!target && scripts.length > 0) target = scripts[scripts.length - 1];
|
|
51
52
|
return {
|
|
52
53
|
apiKey: (target && target.getAttribute('data-api-key')) || '',
|
|
53
|
-
server: (target && target.getAttribute('data-server')) || ''
|
|
54
|
+
server: (target && target.getAttribute('data-server')) || '',
|
|
55
|
+
iconColor: (target && target.getAttribute('data-icon-color')) || '',
|
|
56
|
+
iconShape: (target && target.getAttribute('data-icon-shape')) || ''
|
|
54
57
|
};
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -92,12 +95,21 @@
|
|
|
92
95
|
════════════════════════════════════════════════════════════════ */
|
|
93
96
|
|
|
94
97
|
var _ICONS = {
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
/* Modern chat icon: rounded square with 3 horizontal chat lines */
|
|
99
|
+
chatIcon:
|
|
100
|
+
'<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">' +
|
|
101
|
+
'<rect x="3" y="3" width="18" height="18" rx="5" ry="5" stroke="currentColor" stroke-width="1.8" fill="none"/>' +
|
|
102
|
+
'<line x1="8" y1="9" x2="16" y2="9" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>' +
|
|
103
|
+
'<line x1="8" y1="12.5" x2="14" y2="12.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>' +
|
|
104
|
+
'<line x1="8" y1="16" x2="12" y2="16" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>' +
|
|
105
|
+
'</svg>',
|
|
106
|
+
/* Send arrow */
|
|
97
107
|
send:
|
|
98
108
|
'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>',
|
|
109
|
+
/* Close X */
|
|
99
110
|
close:
|
|
100
|
-
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2
|
|
111
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
|
|
112
|
+
/* Error circle */
|
|
101
113
|
error:
|
|
102
114
|
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>'
|
|
103
115
|
};
|
|
@@ -157,11 +169,12 @@
|
|
|
157
169
|
botName: 'WAKZ',
|
|
158
170
|
welcomeMessage: '',
|
|
159
171
|
primaryColor: '#171717',
|
|
160
|
-
|
|
161
|
-
|
|
172
|
+
iconColor: '#171717',
|
|
173
|
+
iconShape: 'circle',
|
|
174
|
+
chatBg: '#FAFAFA',
|
|
162
175
|
widgetBg: '#ffffff',
|
|
163
176
|
position: 'bottom-right',
|
|
164
|
-
language: '
|
|
177
|
+
language: 'ar',
|
|
165
178
|
showStatus: true,
|
|
166
179
|
online: true
|
|
167
180
|
};
|
|
@@ -182,6 +195,8 @@
|
|
|
182
195
|
self.apiKey = attrs.apiKey;
|
|
183
196
|
self.server = attrs.server;
|
|
184
197
|
self.visitorId = _getVisitorId();
|
|
198
|
+
self._scriptIconColor = attrs.iconColor;
|
|
199
|
+
self._scriptIconShape = attrs.iconShape;
|
|
185
200
|
|
|
186
201
|
/* ── Runtime state ── */
|
|
187
202
|
self.config = Object.assign({}, _DEFAULTS);
|
|
@@ -195,11 +210,13 @@
|
|
|
195
210
|
self._pollTimer = null;
|
|
196
211
|
self._lastPollTime = null;
|
|
197
212
|
self._POLL_INTERVAL = 4000; /* Poll every 4 seconds */
|
|
213
|
+
self._knownMessageIds = {}; /* Deduplication set for message IDs */
|
|
198
214
|
|
|
199
215
|
/* ── DOM refs (populated after mount) ── */
|
|
200
216
|
self._host = null;
|
|
201
217
|
self._shadow = null;
|
|
202
218
|
self._root = null;
|
|
219
|
+
self._overlayEl = null;
|
|
203
220
|
self._chatWindow = null;
|
|
204
221
|
self._messagesContainer = null;
|
|
205
222
|
self._inputEl = null;
|
|
@@ -218,7 +235,7 @@
|
|
|
218
235
|
}
|
|
219
236
|
|
|
220
237
|
/* ════════════════════════════════════════════════════════════════
|
|
221
|
-
CSS — Complete styling injected into Shadow DOM
|
|
238
|
+
CSS — Complete premium styling injected into Shadow DOM
|
|
222
239
|
════════════════════════════════════════════════════════════════ */
|
|
223
240
|
|
|
224
241
|
WAKZWidget.prototype._injectCSS = function () {
|
|
@@ -229,18 +246,14 @@
|
|
|
229
246
|
/* ── CSS Custom Properties (theming) ── */
|
|
230
247
|
':host {',
|
|
231
248
|
' --wakz-primary: #171717;',
|
|
232
|
-
' --wakz-
|
|
233
|
-
' --wakz-chat-bg: #
|
|
249
|
+
' --wakz-icon-color: #171717;',
|
|
250
|
+
' --wakz-chat-bg: #FAFAFA;',
|
|
234
251
|
' --wakz-widget-bg: #ffffff;',
|
|
235
|
-
' --wakz-radius
|
|
236
|
-
' --wakz-radius-bubble:
|
|
237
|
-
' --wakz-radius-fab: 28px;',
|
|
238
|
-
' --wakz-shadow-sm: 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06);',
|
|
239
|
-
' --wakz-shadow-md: 0 4px 12px rgba(0,0,0,.1), 0 2px 4px rgba(0,0,0,.06);',
|
|
240
|
-
' --wakz-shadow-lg: 0 12px 40px rgba(0,0,0,.15), 0 4px 12px rgba(0,0,0,.1);',
|
|
241
|
-
' --wakz-shadow-xl: 0 20px 60px rgba(0,0,0,.2), 0 8px 20px rgba(0,0,0,.12);',
|
|
242
|
-
' --wakz-transition: 200ms ease;',
|
|
252
|
+
' --wakz-radius: 20px;',
|
|
253
|
+
' --wakz-radius-bubble: 18px;',
|
|
243
254
|
' --wakz-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;',
|
|
255
|
+
' --wakz-shadow: 0 25px 60px rgba(0,0,0,0.15), 0 8px 20px rgba(0,0,0,0.1);',
|
|
256
|
+
' --wakz-shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);',
|
|
244
257
|
' all: initial;',
|
|
245
258
|
' font-family: var(--wakz-font);',
|
|
246
259
|
'}',
|
|
@@ -258,34 +271,46 @@
|
|
|
258
271
|
'.wakz-fab {',
|
|
259
272
|
' position: fixed;',
|
|
260
273
|
' z-index: 2147483647;',
|
|
261
|
-
' width:
|
|
262
|
-
' height:
|
|
263
|
-
' border-radius:
|
|
274
|
+
' width: 52px;',
|
|
275
|
+
' height: 52px;',
|
|
276
|
+
' border-radius: 50%;',
|
|
264
277
|
' border: none;',
|
|
265
278
|
' cursor: pointer;',
|
|
266
279
|
' display: flex;',
|
|
267
280
|
' align-items: center;',
|
|
268
281
|
' justify-content: center;',
|
|
269
|
-
' box-shadow:
|
|
270
|
-
' transition: transform
|
|
282
|
+
' box-shadow: 0 12px 40px rgba(0,0,0,0.15), 0 4px 12px rgba(0,0,0,0.1);',
|
|
283
|
+
' transition: opacity 0.3s cubic-bezier(0.16,1,0.3,1), transform 0.3s cubic-bezier(0.16,1,0.3,1), box-shadow 0.2s ease;',
|
|
271
284
|
' outline: none;',
|
|
272
285
|
' -webkit-tap-highlight-color: transparent;',
|
|
273
|
-
' background: var(--wakz-
|
|
286
|
+
' background: var(--wakz-icon-color, #171717);',
|
|
274
287
|
'}',
|
|
275
288
|
'.wakz-fab:hover {',
|
|
276
289
|
' transform: scale(1.08);',
|
|
277
|
-
' box-shadow:
|
|
290
|
+
' box-shadow: 0 20px 60px rgba(0,0,0,0.2), 0 8px 20px rgba(0,0,0,0.12);',
|
|
278
291
|
'}',
|
|
279
292
|
'.wakz-fab:active {',
|
|
280
293
|
' transform: scale(0.96);',
|
|
281
294
|
'}',
|
|
282
295
|
'.wakz-fab svg {',
|
|
283
|
-
' width:
|
|
284
|
-
' height:
|
|
296
|
+
' width: 24px;',
|
|
297
|
+
' height: 24px;',
|
|
285
298
|
' color: #ffffff;',
|
|
286
299
|
' pointer-events: none;',
|
|
287
300
|
'}',
|
|
288
301
|
|
|
302
|
+
/* ── FAB Shape: rounded square ── */
|
|
303
|
+
'.wakz-fab-shape-rounded {',
|
|
304
|
+
' border-radius: 16px;',
|
|
305
|
+
'}',
|
|
306
|
+
|
|
307
|
+
/* ── FAB hidden when chat is open ── */
|
|
308
|
+
'.wakz-fab-hidden {',
|
|
309
|
+
' opacity: 0;',
|
|
310
|
+
' pointer-events: none;',
|
|
311
|
+
' transform: scale(0.8);',
|
|
312
|
+
'}',
|
|
313
|
+
|
|
289
314
|
/* ── FAB Positioning ── */
|
|
290
315
|
'.wakz-fab-pos-br { bottom: 24px; right: 24px; }',
|
|
291
316
|
'.wakz-fab-pos-bl { bottom: 24px; left: 24px; }',
|
|
@@ -306,18 +331,18 @@
|
|
|
306
331
|
══════════════════════════════════════════════════ */
|
|
307
332
|
'.wakz-fab-dot {',
|
|
308
333
|
' position: absolute;',
|
|
309
|
-
' top:
|
|
310
|
-
' right:
|
|
311
|
-
' width:
|
|
312
|
-
' height:
|
|
334
|
+
' top: -1px;',
|
|
335
|
+
' right: -1px;',
|
|
336
|
+
' width: 12px;',
|
|
337
|
+
' height: 12px;',
|
|
313
338
|
' border-radius: 50%;',
|
|
314
|
-
' border:
|
|
339
|
+
' border: 2px solid #ffffff;',
|
|
315
340
|
' z-index: 2;',
|
|
316
341
|
' transition: background 0.3s ease;',
|
|
317
342
|
'}',
|
|
318
343
|
'.wakz-fab-pos-bl .wakz-fab-dot {',
|
|
319
344
|
' right: auto;',
|
|
320
|
-
' left:
|
|
345
|
+
' left: -1px;',
|
|
321
346
|
'}',
|
|
322
347
|
|
|
323
348
|
/* ── Online (green pulse) ── */
|
|
@@ -338,45 +363,60 @@
|
|
|
338
363
|
' animation: none;',
|
|
339
364
|
'}',
|
|
340
365
|
|
|
341
|
-
/*
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
'
|
|
366
|
+
/* ══════════════════════════════════════════════════
|
|
367
|
+
OVERLAY BACKDROP
|
|
368
|
+
══════════════════════════════════════════════════ */
|
|
369
|
+
'.wakz-overlay {',
|
|
370
|
+
' position: fixed;',
|
|
371
|
+
' top: 0;',
|
|
372
|
+
' left: 0;',
|
|
373
|
+
' right: 0;',
|
|
374
|
+
' bottom: 0;',
|
|
375
|
+
' z-index: 2147483645;',
|
|
376
|
+
' background: rgba(0,0,0,0.4);',
|
|
377
|
+
' -webkit-backdrop-filter: blur(4px);',
|
|
378
|
+
' backdrop-filter: blur(4px);',
|
|
379
|
+
' opacity: 0;',
|
|
380
|
+
' pointer-events: none;',
|
|
381
|
+
' transition: opacity 0.3s cubic-bezier(0.16,1,0.3,1);',
|
|
382
|
+
'}',
|
|
383
|
+
'.wakz-overlay.wakz-visible {',
|
|
384
|
+
' opacity: 1;',
|
|
385
|
+
' pointer-events: auto;',
|
|
345
386
|
'}',
|
|
346
387
|
|
|
347
388
|
/* ══════════════════════════════════════════════════
|
|
348
|
-
CHAT WINDOW
|
|
389
|
+
CHAT WINDOW — Centered Modal
|
|
349
390
|
══════════════════════════════════════════════════ */
|
|
350
391
|
'.wakz-window {',
|
|
351
392
|
' position: fixed;',
|
|
352
393
|
' z-index: 2147483646;',
|
|
353
|
-
'
|
|
354
|
-
'
|
|
355
|
-
'
|
|
356
|
-
'
|
|
394
|
+
' top: 50%;',
|
|
395
|
+
' left: 50%;',
|
|
396
|
+
' transform: translate(-50%, -50%) scale(0.95);',
|
|
397
|
+
' width: 420px;',
|
|
398
|
+
' max-height: 75vh;',
|
|
399
|
+
' border-radius: 20px;',
|
|
357
400
|
' overflow: hidden;',
|
|
358
401
|
' display: flex;',
|
|
359
402
|
' flex-direction: column;',
|
|
360
403
|
' background: var(--wakz-widget-bg);',
|
|
361
|
-
'
|
|
404
|
+
' border: 1px solid #E5E5E5;',
|
|
405
|
+
' box-shadow: 0 25px 60px rgba(0,0,0,0.15), 0 8px 20px rgba(0,0,0,0.1);',
|
|
406
|
+
' -webkit-backdrop-filter: blur(20px);',
|
|
407
|
+
' backdrop-filter: blur(20px);',
|
|
362
408
|
' opacity: 0;',
|
|
363
|
-
' transform: translateY(20px) scale(0.95);',
|
|
364
409
|
' pointer-events: none;',
|
|
365
|
-
' transition: opacity 0.3s cubic-bezier(0.
|
|
366
|
-
' transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);',
|
|
410
|
+
' transition: opacity 0.3s cubic-bezier(0.16,1,0.3,1), transform 0.3s cubic-bezier(0.16,1,0.3,1);',
|
|
367
411
|
'}',
|
|
368
412
|
'.wakz-window.wakz-visible {',
|
|
369
413
|
' opacity: 1;',
|
|
370
|
-
' transform:
|
|
414
|
+
' transform: translate(-50%, -50%) scale(1);',
|
|
371
415
|
' pointer-events: auto;',
|
|
372
416
|
'}',
|
|
373
417
|
|
|
374
|
-
/* ── Window Positioning ── */
|
|
375
|
-
'.wakz-win-pos-br { bottom: 92px; right: 24px; }',
|
|
376
|
-
'.wakz-win-pos-bl { bottom: 92px; left: 24px; }',
|
|
377
|
-
|
|
378
418
|
/* ══════════════════════════════════════════════════
|
|
379
|
-
HEADER
|
|
419
|
+
HEADER — Glassmorphism (matching floatingGlassShell)
|
|
380
420
|
══════════════════════════════════════════════════ */
|
|
381
421
|
'.wakz-hdr {',
|
|
382
422
|
' flex-shrink: 0;',
|
|
@@ -384,30 +424,35 @@
|
|
|
384
424
|
' display: flex;',
|
|
385
425
|
' align-items: center;',
|
|
386
426
|
' justify-content: space-between;',
|
|
387
|
-
' color: #
|
|
427
|
+
' color: #171717;',
|
|
388
428
|
' cursor: default;',
|
|
389
429
|
' user-select: none;',
|
|
390
|
-
' background:
|
|
430
|
+
' background: rgba(255,255,255,0.85);',
|
|
431
|
+
' -webkit-backdrop-filter: blur(20px);',
|
|
432
|
+
' backdrop-filter: blur(20px);',
|
|
433
|
+
' border-bottom: 1px solid rgba(0,0,0,0.06);',
|
|
434
|
+
' height: 60px;',
|
|
391
435
|
'}',
|
|
392
|
-
'.wakz-hdr-
|
|
436
|
+
'.wakz-hdr-right {',
|
|
393
437
|
' display: flex;',
|
|
394
438
|
' align-items: center;',
|
|
395
439
|
' gap: 11px;',
|
|
396
440
|
' min-width: 0;',
|
|
397
441
|
'}',
|
|
398
442
|
|
|
399
|
-
/* ── Bot Avatar ── */
|
|
443
|
+
/* ── Bot Avatar (header) ── */
|
|
400
444
|
'.wakz-avatar {',
|
|
401
|
-
' width:
|
|
402
|
-
' height:
|
|
445
|
+
' width: 36px;',
|
|
446
|
+
' height: 36px;',
|
|
403
447
|
' border-radius: 50%;',
|
|
404
|
-
' background:
|
|
448
|
+
' background: var(--wakz-primary);',
|
|
405
449
|
' display: flex;',
|
|
406
450
|
' align-items: center;',
|
|
407
451
|
' justify-content: center;',
|
|
408
452
|
' font-weight: 700;',
|
|
409
|
-
' font-size:
|
|
453
|
+
' font-size: 15px;',
|
|
410
454
|
' flex-shrink: 0;',
|
|
455
|
+
' color: #ffffff;',
|
|
411
456
|
' letter-spacing: 0.5px;',
|
|
412
457
|
'}',
|
|
413
458
|
|
|
@@ -418,20 +463,27 @@
|
|
|
418
463
|
' min-width: 0;',
|
|
419
464
|
'}',
|
|
420
465
|
'.wakz-hdr-name {',
|
|
421
|
-
' font-size:
|
|
466
|
+
' font-size: 16px;',
|
|
422
467
|
' font-weight: 600;',
|
|
423
468
|
' line-height: 1.3;',
|
|
469
|
+
' color: #171717;',
|
|
424
470
|
' white-space: nowrap;',
|
|
425
471
|
' overflow: hidden;',
|
|
426
472
|
' text-overflow: ellipsis;',
|
|
427
473
|
'}',
|
|
474
|
+
|
|
475
|
+
/* ── Header Left: status + close ── */
|
|
476
|
+
'.wakz-hdr-left {',
|
|
477
|
+
' display: flex;',
|
|
478
|
+
' align-items: center;',
|
|
479
|
+
' gap: 10px;',
|
|
480
|
+
'}',
|
|
428
481
|
'.wakz-hdr-status {',
|
|
429
482
|
' font-size: 12px;',
|
|
430
|
-
'
|
|
483
|
+
' color: #171717;',
|
|
431
484
|
' display: flex;',
|
|
432
485
|
' align-items: center;',
|
|
433
486
|
' gap: 5px;',
|
|
434
|
-
' margin-top: 2px;',
|
|
435
487
|
'}',
|
|
436
488
|
'.wakz-hdr-status-dot {',
|
|
437
489
|
' width: 7px;',
|
|
@@ -441,8 +493,8 @@
|
|
|
441
493
|
' flex-shrink: 0;',
|
|
442
494
|
' transition: background 0.3s ease;',
|
|
443
495
|
'}',
|
|
444
|
-
'.wakz-hdr-status-dot.online { background: #
|
|
445
|
-
'.wakz-hdr-status-dot.offline { background: #
|
|
496
|
+
'.wakz-hdr-status-dot.online { background: #22c55e; }',
|
|
497
|
+
'.wakz-hdr-status-dot.offline { background: #ef4444; }',
|
|
446
498
|
|
|
447
499
|
/* ── Close Button ── */
|
|
448
500
|
'.wakz-close {',
|
|
@@ -450,18 +502,18 @@
|
|
|
450
502
|
' height: 32px;',
|
|
451
503
|
' border-radius: 50%;',
|
|
452
504
|
' border: none;',
|
|
453
|
-
' background:
|
|
454
|
-
' color: #
|
|
505
|
+
' background: transparent;',
|
|
506
|
+
' color: #171717;',
|
|
455
507
|
' cursor: pointer;',
|
|
456
508
|
' display: flex;',
|
|
457
509
|
' align-items: center;',
|
|
458
510
|
' justify-content: center;',
|
|
459
|
-
' transition: background
|
|
511
|
+
' transition: background 0.2s ease;',
|
|
460
512
|
' outline: none;',
|
|
461
513
|
' flex-shrink: 0;',
|
|
462
514
|
'}',
|
|
463
515
|
'.wakz-close:hover {',
|
|
464
|
-
' background: rgba(
|
|
516
|
+
' background: rgba(0,0,0,0.06);',
|
|
465
517
|
'}',
|
|
466
518
|
'.wakz-close svg {',
|
|
467
519
|
' width: 18px;',
|
|
@@ -475,90 +527,130 @@
|
|
|
475
527
|
' flex: 1;',
|
|
476
528
|
' overflow-y: auto;',
|
|
477
529
|
' overflow-x: hidden;',
|
|
478
|
-
' padding:
|
|
530
|
+
' padding: 16px;',
|
|
479
531
|
' display: flex;',
|
|
480
532
|
' flex-direction: column;',
|
|
481
|
-
' gap:
|
|
533
|
+
' gap: 8px;',
|
|
482
534
|
' scroll-behavior: smooth;',
|
|
483
535
|
' background: var(--wakz-chat-bg);',
|
|
484
536
|
'}',
|
|
485
537
|
'.wakz-msgs::-webkit-scrollbar { width: 5px; }',
|
|
486
538
|
'.wakz-msgs::-webkit-scrollbar-track { background: transparent; }',
|
|
487
539
|
'.wakz-msgs::-webkit-scrollbar-thumb {',
|
|
488
|
-
' background: rgba(0,0,0,0.
|
|
540
|
+
' background: rgba(0,0,0,0.08);',
|
|
489
541
|
' border-radius: 10px;',
|
|
490
542
|
'}',
|
|
491
543
|
|
|
492
544
|
/* ══════════════════════════════════════════════════
|
|
493
|
-
MESSAGE
|
|
545
|
+
MESSAGE STYLES
|
|
494
546
|
══════════════════════════════════════════════════ */
|
|
495
|
-
'.wakz-msg-row {',
|
|
496
|
-
' display: flex;',
|
|
497
|
-
' max-width: 82%;',
|
|
498
|
-
' animation: wakz-msg-in 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;',
|
|
499
|
-
'}',
|
|
500
|
-
'.wakz-msg-row.bot { align-self: flex-start; }',
|
|
501
|
-
'.wakz-msg-row.user { align-self: flex-end; }',
|
|
502
|
-
|
|
503
547
|
'@keyframes wakz-msg-in {',
|
|
504
548
|
' 0% { opacity: 0; transform: translateY(8px); }',
|
|
505
549
|
' 100% { opacity: 1; transform: translateY(0); }',
|
|
506
550
|
'}',
|
|
507
551
|
|
|
508
|
-
|
|
509
|
-
'
|
|
552
|
+
/* ── User Message (bubble, right-aligned LTR / left-aligned RTL) ── */
|
|
553
|
+
'.wakz-msg-row {',
|
|
554
|
+
' animation: wakz-msg-in 0.3s cubic-bezier(0.16,1,0.3,1) forwards;',
|
|
555
|
+
'}',
|
|
556
|
+
'.wakz-msg-row.user {',
|
|
557
|
+
' align-self: flex-end;',
|
|
558
|
+
' max-width: 75%;',
|
|
559
|
+
'}',
|
|
560
|
+
'.wakz-msg-row.bot {',
|
|
561
|
+
' align-self: flex-start;',
|
|
562
|
+
' max-width: 85%;',
|
|
563
|
+
'}',
|
|
564
|
+
|
|
565
|
+
'.wakz-bubble-user {',
|
|
566
|
+
' padding: 10px 16px;',
|
|
510
567
|
' font-size: 14px;',
|
|
511
568
|
' line-height: 1.55;',
|
|
512
569
|
' word-wrap: break-word;',
|
|
513
570
|
' overflow-wrap: break-word;',
|
|
514
571
|
' white-space: pre-wrap;',
|
|
515
|
-
' border-radius:
|
|
516
|
-
' position: relative;',
|
|
517
|
-
'}',
|
|
518
|
-
'.wakz-bubble.bot {',
|
|
519
|
-
' background: #ffffff;',
|
|
520
|
-
' color: #1a1a1a;',
|
|
521
|
-
' border-bottom-left-radius: 4px;',
|
|
522
|
-
' box-shadow: var(--wakz-shadow-sm);',
|
|
523
|
-
'}',
|
|
524
|
-
'.wakz-bubble.user {',
|
|
525
|
-
' color: #ffffff;',
|
|
526
|
-
' border-bottom-right-radius: 4px;',
|
|
572
|
+
' border-radius: 18px 18px 6px 18px;',
|
|
527
573
|
' background: var(--wakz-primary);',
|
|
528
|
-
'
|
|
529
|
-
'.wakz-bubble.error-bubble {',
|
|
530
|
-
' background: #fef2f2;',
|
|
531
|
-
' color: #dc2626;',
|
|
532
|
-
' border: 1px solid #fecaca;',
|
|
533
|
-
' border-bottom-left-radius: 4px;',
|
|
534
|
-
' box-shadow: none;',
|
|
574
|
+
' color: #ffffff;',
|
|
535
575
|
'}',
|
|
536
576
|
|
|
537
|
-
/* ──
|
|
577
|
+
/* ── Timestamp ── */
|
|
538
578
|
'.wakz-ts {',
|
|
539
579
|
' display: block;',
|
|
540
|
-
' font-size:
|
|
541
|
-
' opacity: 0.
|
|
580
|
+
' font-size: 10px;',
|
|
581
|
+
' opacity: 0.6;',
|
|
542
582
|
' margin-top: 4px;',
|
|
543
583
|
'}',
|
|
544
|
-
'.wakz-bubble
|
|
584
|
+
'.wakz-bubble-user .wakz-ts {',
|
|
545
585
|
' text-align: right;',
|
|
586
|
+
' color: rgba(255,255,255,0.7);',
|
|
546
587
|
'}',
|
|
547
588
|
|
|
548
|
-
/* ──
|
|
589
|
+
/* ── Bot Message (no bubble, flat on bg with left accent border) ── */
|
|
590
|
+
'.wakz-msg-bot-wrap {',
|
|
591
|
+
' display: flex;',
|
|
592
|
+
' flex-direction: column;',
|
|
593
|
+
' gap: 2px;',
|
|
594
|
+
'}',
|
|
595
|
+
'.wakz-msg-bot-avatar {',
|
|
596
|
+
' width: 20px;',
|
|
597
|
+
' height: 20px;',
|
|
598
|
+
' border-radius: 50%;',
|
|
599
|
+
' background: var(--wakz-primary);',
|
|
600
|
+
' display: flex;',
|
|
601
|
+
' align-items: center;',
|
|
602
|
+
' justify-content: center;',
|
|
603
|
+
' color: #ffffff;',
|
|
604
|
+
' font-weight: 700;',
|
|
605
|
+
' font-size: 9px;',
|
|
606
|
+
' flex-shrink: 0;',
|
|
607
|
+
' margin-bottom: 2px;',
|
|
608
|
+
'}',
|
|
609
|
+
'.wakz-msg-bot-content {',
|
|
610
|
+
' border-left: 3px solid rgba(23,23,23,0.2);',
|
|
611
|
+
' padding: 10px 0 10px 14px;',
|
|
612
|
+
' font-size: 14px;',
|
|
613
|
+
' line-height: 1.6;',
|
|
614
|
+
' color: #374151;',
|
|
615
|
+
' word-wrap: break-word;',
|
|
616
|
+
' overflow-wrap: break-word;',
|
|
617
|
+
' white-space: pre-wrap;',
|
|
618
|
+
'}',
|
|
619
|
+
'.wakz-msg-bot-content .wakz-ts {',
|
|
620
|
+
' color: #9CA3AF;',
|
|
621
|
+
'}',
|
|
622
|
+
|
|
623
|
+
/* ── Support / Human Agent Message ── */
|
|
624
|
+
'.wakz-msg-bot-content.wakz-human {',
|
|
625
|
+
' border-left-color: #16A34A;',
|
|
626
|
+
'}',
|
|
549
627
|
'.wakz-human-badge {',
|
|
550
|
-
' display: inline-
|
|
551
|
-
'
|
|
552
|
-
'
|
|
628
|
+
' display: inline-flex;',
|
|
629
|
+
' align-items: center;',
|
|
630
|
+
' font-size: 11px;',
|
|
631
|
+
' background: rgba(22,163,74,0.08);',
|
|
553
632
|
' color: #16A34A;',
|
|
554
|
-
' border-radius:
|
|
555
|
-
' padding:
|
|
556
|
-
' margin-top:
|
|
633
|
+
' border-radius: 20px;',
|
|
634
|
+
' padding: 2px 8px;',
|
|
635
|
+
' margin-top: 6px;',
|
|
557
636
|
' font-weight: 600;',
|
|
558
|
-
' letter-spacing: 0.2px;',
|
|
559
637
|
'}',
|
|
560
638
|
|
|
561
|
-
/* ──
|
|
639
|
+
/* ── Error bubble ── */
|
|
640
|
+
'.wakz-bubble-error {',
|
|
641
|
+
' padding: 10px 14px;',
|
|
642
|
+
' font-size: 14px;',
|
|
643
|
+
' line-height: 1.55;',
|
|
644
|
+
' word-wrap: break-word;',
|
|
645
|
+
' overflow-wrap: break-word;',
|
|
646
|
+
' white-space: pre-wrap;',
|
|
647
|
+
' background: #FEF2F2;',
|
|
648
|
+
' color: #DC2626;',
|
|
649
|
+
' border: 1px solid #FECACA;',
|
|
650
|
+
' border-radius: 18px 18px 6px 18px;',
|
|
651
|
+
'}',
|
|
652
|
+
|
|
653
|
+
/* ── Retry Button (inside error) ── */
|
|
562
654
|
'.wakz-retry {',
|
|
563
655
|
' display: inline-flex;',
|
|
564
656
|
' align-items: center;',
|
|
@@ -573,7 +665,7 @@
|
|
|
573
665
|
' color: #dc2626;',
|
|
574
666
|
' cursor: pointer;',
|
|
575
667
|
' font-family: var(--wakz-font);',
|
|
576
|
-
' transition: background
|
|
668
|
+
' transition: background 0.2s ease, border-color 0.2s ease;',
|
|
577
669
|
' outline: none;',
|
|
578
670
|
'}',
|
|
579
671
|
'.wakz-retry:hover {',
|
|
@@ -582,23 +674,32 @@
|
|
|
582
674
|
'}',
|
|
583
675
|
|
|
584
676
|
/* ══════════════════════════════════════════════════
|
|
585
|
-
|
|
677
|
+
WELCOME MESSAGE
|
|
678
|
+
══════════════════════════════════════════════════ */
|
|
679
|
+
'.wakz-welcome-wrap {',
|
|
680
|
+
' display: flex;',
|
|
681
|
+
' flex-direction: column;',
|
|
682
|
+
' gap: 2px;',
|
|
683
|
+
' max-width: 85%;',
|
|
684
|
+
' align-self: flex-start;',
|
|
685
|
+
' animation: wakz-msg-in 0.35s cubic-bezier(0.16,1,0.3,1) forwards;',
|
|
686
|
+
'}',
|
|
687
|
+
|
|
688
|
+
/* ══════════════════════════════════════════════════
|
|
689
|
+
TYPING INDICATOR (no bubble — on background)
|
|
586
690
|
══════════════════════════════════════════════════ */
|
|
587
691
|
'.wakz-typing {',
|
|
588
692
|
' display: flex;',
|
|
589
693
|
' align-items: center;',
|
|
590
694
|
' gap: 4px;',
|
|
591
|
-
' padding: 14px
|
|
592
|
-
'
|
|
593
|
-
' border-radius: var(--wakz-radius-bubble);',
|
|
594
|
-
' border-bottom-left-radius: 4px;',
|
|
595
|
-
' box-shadow: var(--wakz-shadow-sm);',
|
|
695
|
+
' padding: 10px 0 10px 14px;',
|
|
696
|
+
' border-left: 3px solid rgba(23,23,23,0.2);',
|
|
596
697
|
'}',
|
|
597
698
|
'.wakz-typing-dot {',
|
|
598
699
|
' width: 7px;',
|
|
599
700
|
' height: 7px;',
|
|
600
701
|
' border-radius: 50%;',
|
|
601
|
-
' background: #
|
|
702
|
+
' background: #9CA3AF;',
|
|
602
703
|
' animation: wakz-bounce-dot 1.4s ease-in-out infinite;',
|
|
603
704
|
'}',
|
|
604
705
|
'.wakz-typing-dot:nth-child(2) { animation-delay: 0.16s; }',
|
|
@@ -609,31 +710,33 @@
|
|
|
609
710
|
'}',
|
|
610
711
|
|
|
611
712
|
/* ══════════════════════════════════════════════════
|
|
612
|
-
INPUT AREA
|
|
713
|
+
INPUT AREA — Glass Effect
|
|
613
714
|
══════════════════════════════════════════════════ */
|
|
614
715
|
'.wakz-input-wrap {',
|
|
615
716
|
' flex-shrink: 0;',
|
|
616
|
-
' padding: 12px
|
|
617
|
-
' border-top: 1px solid
|
|
618
|
-
' background:
|
|
717
|
+
' padding: 12px 16px;',
|
|
718
|
+
' border-top: 1px solid rgba(0,0,0,0.06);',
|
|
719
|
+
' background: rgba(255,255,255,0.7);',
|
|
720
|
+
' -webkit-backdrop-filter: blur(12px);',
|
|
721
|
+
' backdrop-filter: blur(12px);',
|
|
619
722
|
' display: flex;',
|
|
620
723
|
' align-items: flex-end;',
|
|
621
724
|
' gap: 8px;',
|
|
622
725
|
'}',
|
|
623
726
|
'.wakz-input {',
|
|
624
727
|
' flex: 1;',
|
|
625
|
-
' border: 1.5px solid #
|
|
626
|
-
' border-radius:
|
|
728
|
+
' border: 1.5px solid #E5E5E5;',
|
|
729
|
+
' border-radius: 14px;',
|
|
627
730
|
' padding: 10px 16px;',
|
|
628
731
|
' font-size: 14px;',
|
|
629
732
|
' line-height: 1.4;',
|
|
630
733
|
' outline: none;',
|
|
631
734
|
' font-family: var(--wakz-font);',
|
|
632
|
-
' transition: border-color
|
|
735
|
+
' transition: border-color 0.2s ease, background 0.2s ease;',
|
|
633
736
|
' resize: none;',
|
|
634
737
|
' max-height: 100px;',
|
|
635
738
|
' overflow-y: auto;',
|
|
636
|
-
' background: #
|
|
739
|
+
' background: #F9FAFB;',
|
|
637
740
|
' color: #111827;',
|
|
638
741
|
'}',
|
|
639
742
|
'.wakz-input:focus {',
|
|
@@ -644,10 +747,10 @@
|
|
|
644
747
|
' color: #9ca3af;',
|
|
645
748
|
'}',
|
|
646
749
|
|
|
647
|
-
/* ── Send Button ── */
|
|
750
|
+
/* ── Send Button (floating circle, separate from textarea) ── */
|
|
648
751
|
'.wakz-send {',
|
|
649
|
-
' width:
|
|
650
|
-
' height:
|
|
752
|
+
' width: 44px;',
|
|
753
|
+
' height: 44px;',
|
|
651
754
|
' border-radius: 50%;',
|
|
652
755
|
' border: none;',
|
|
653
756
|
' color: #ffffff;',
|
|
@@ -656,20 +759,21 @@
|
|
|
656
759
|
' align-items: center;',
|
|
657
760
|
' justify-content: center;',
|
|
658
761
|
' flex-shrink: 0;',
|
|
659
|
-
' transition: opacity
|
|
762
|
+
' transition: opacity 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;',
|
|
660
763
|
' outline: none;',
|
|
661
764
|
' background: var(--wakz-primary);',
|
|
662
|
-
' box-shadow:
|
|
765
|
+
' box-shadow: 0 2px 8px rgba(0,0,0,0.12);',
|
|
663
766
|
'}',
|
|
664
767
|
'.wakz-send:hover:not(:disabled) {',
|
|
665
|
-
'
|
|
768
|
+
' transform: scale(1.05);',
|
|
769
|
+
' box-shadow: 0 4px 16px rgba(0,0,0,0.18);',
|
|
666
770
|
'}',
|
|
667
771
|
'.wakz-send:disabled {',
|
|
668
772
|
' opacity: 0.35;',
|
|
669
773
|
' cursor: not-allowed;',
|
|
670
774
|
'}',
|
|
671
775
|
'.wakz-send:not(:disabled):active {',
|
|
672
|
-
' transform: scale(0.
|
|
776
|
+
' transform: scale(0.92);',
|
|
673
777
|
'}',
|
|
674
778
|
'.wakz-send svg {',
|
|
675
779
|
' width: 18px;',
|
|
@@ -677,69 +781,56 @@
|
|
|
677
781
|
' pointer-events: none;',
|
|
678
782
|
'}',
|
|
679
783
|
|
|
680
|
-
/* ══════════════════════════════════════════════════
|
|
681
|
-
WELCOME MESSAGE
|
|
682
|
-
══════════════════════════════════════════════════ */
|
|
683
|
-
'.wakz-welcome-wrap {',
|
|
684
|
-
' display: flex;',
|
|
685
|
-
' gap: 8px;',
|
|
686
|
-
' max-width: 88%;',
|
|
687
|
-
' align-self: flex-start;',
|
|
688
|
-
' animation: wakz-msg-in 0.35s cubic-bezier(0.4, 0, 0.2, 1) forwards;',
|
|
689
|
-
'}',
|
|
690
|
-
'.wakz-welcome-avatar {',
|
|
691
|
-
' width: 28px;',
|
|
692
|
-
' height: 28px;',
|
|
693
|
-
' border-radius: 50%;',
|
|
694
|
-
' background: var(--wakz-primary);',
|
|
695
|
-
' display: flex;',
|
|
696
|
-
' align-items: center;',
|
|
697
|
-
' justify-content: center;',
|
|
698
|
-
' color: #ffffff;',
|
|
699
|
-
' font-weight: 700;',
|
|
700
|
-
' font-size: 12px;',
|
|
701
|
-
' flex-shrink: 0;',
|
|
702
|
-
' align-self: flex-end;',
|
|
703
|
-
' margin-bottom: 2px;',
|
|
704
|
-
'}',
|
|
705
|
-
|
|
706
784
|
/* ══════════════════════════════════════════════════
|
|
707
785
|
RTL SUPPORT
|
|
708
786
|
══════════════════════════════════════════════════ */
|
|
709
787
|
'.wakz-rtl { direction: rtl; }',
|
|
710
|
-
'.wakz-rtl .wakz-bubble
|
|
711
|
-
' border-
|
|
712
|
-
'
|
|
788
|
+
'.wakz-rtl .wakz-bubble-user {',
|
|
789
|
+
' border-radius: 18px 18px 18px 6px;',
|
|
790
|
+
'}',
|
|
791
|
+
'.wakz-rtl .wakz-bubble-user .wakz-ts {',
|
|
792
|
+
' text-align: left;',
|
|
793
|
+
'}',
|
|
794
|
+
'.wakz-rtl .wakz-msg-bot-content {',
|
|
795
|
+
' border-left: none;',
|
|
796
|
+
' border-right: 3px solid rgba(23,23,23,0.2);',
|
|
797
|
+
' padding: 10px 14px 10px 0;',
|
|
713
798
|
'}',
|
|
714
|
-
'.wakz-rtl .wakz-
|
|
715
|
-
' border-
|
|
716
|
-
' border-
|
|
799
|
+
'.wakz-rtl .wakz-msg-bot-content.wakz-human {',
|
|
800
|
+
' border-left: none;',
|
|
801
|
+
' border-right-color: #16A34A;',
|
|
717
802
|
'}',
|
|
718
803
|
'.wakz-rtl .wakz-typing {',
|
|
719
|
-
' border-
|
|
720
|
-
' border-
|
|
804
|
+
' border-left: none;',
|
|
805
|
+
' border-right: 3px solid rgba(23,23,23,0.2);',
|
|
806
|
+
' padding: 10px 14px 10px 0;',
|
|
721
807
|
'}',
|
|
722
|
-
'.wakz-rtl .wakz-bubble
|
|
723
|
-
'
|
|
808
|
+
'.wakz-rtl .wakz-bubble-error {',
|
|
809
|
+
' border-radius: 18px 18px 18px 6px;',
|
|
724
810
|
'}',
|
|
725
811
|
|
|
726
812
|
/* ══════════════════════════════════════════════════
|
|
727
|
-
MOBILE RESPONSIVE
|
|
813
|
+
MOBILE RESPONSIVE (< 480px)
|
|
728
814
|
══════════════════════════════════════════════════ */
|
|
729
815
|
'@media (max-width: 480px) {',
|
|
816
|
+
' .wakz-overlay {',
|
|
817
|
+
' display: none;',
|
|
818
|
+
' }',
|
|
730
819
|
' .wakz-window {',
|
|
820
|
+
' top: auto !important;',
|
|
821
|
+
' left: 0 !important;',
|
|
822
|
+
' right: 0 !important;',
|
|
823
|
+
' bottom: 0 !important;',
|
|
731
824
|
' width: 100vw !important;',
|
|
732
825
|
' height: 100vh !important;',
|
|
733
|
-
' min-height: 100vh !important;',
|
|
734
826
|
' max-height: 100vh !important;',
|
|
735
|
-
'
|
|
736
|
-
'
|
|
737
|
-
'
|
|
738
|
-
'
|
|
739
|
-
' border-radius: 0 !important;',
|
|
827
|
+
' border-radius: 20px 20px 0 0 !important;',
|
|
828
|
+
' transform: translateY(100%) !important;',
|
|
829
|
+
' border: none !important;',
|
|
830
|
+
' border-top: 1px solid #E5E5E5 !important;',
|
|
740
831
|
' }',
|
|
741
832
|
' .wakz-window.wakz-visible {',
|
|
742
|
-
' transform: translateY(0)
|
|
833
|
+
' transform: translateY(0) !important;',
|
|
743
834
|
' }',
|
|
744
835
|
' .wakz-fab-pos-br { bottom: 16px; right: 16px; }',
|
|
745
836
|
' .wakz-fab-pos-bl { bottom: 16px; left: 16px; }',
|
|
@@ -756,6 +847,8 @@
|
|
|
756
847
|
var isRtl = self.config.language === 'ar';
|
|
757
848
|
var posClass = self.config.position === 'bottom-left' ? 'bl' : 'br';
|
|
758
849
|
var str = _strings(self.config.language);
|
|
850
|
+
var iconColor = self._scriptIconColor || self.config.iconColor;
|
|
851
|
+
var iconShape = self._scriptIconShape || self.config.iconShape;
|
|
759
852
|
|
|
760
853
|
/* ── Host + Shadow DOM ── */
|
|
761
854
|
self._host = _el('div');
|
|
@@ -768,11 +861,16 @@
|
|
|
768
861
|
self._shadow.appendChild(self._root);
|
|
769
862
|
|
|
770
863
|
/* ══════════════ FLOATING ACTION BUTTON ══════════════ */
|
|
864
|
+
var fabClasses = 'wakz-fab wakz-fab-pos-' + posClass;
|
|
865
|
+
if (iconShape === 'rounded') fabClasses += ' wakz-fab-shape-rounded';
|
|
866
|
+
|
|
771
867
|
self._toggleBtn = _el('button', {
|
|
772
|
-
className:
|
|
773
|
-
'aria-label': str.openChat
|
|
868
|
+
className: fabClasses,
|
|
869
|
+
'aria-label': str.openChat,
|
|
870
|
+
style: { '--wakz-icon-color': iconColor }
|
|
774
871
|
});
|
|
775
|
-
self._toggleBtn.
|
|
872
|
+
self._toggleBtn.style.background = iconColor;
|
|
873
|
+
self._toggleBtn.innerHTML = _ICONS.chatIcon;
|
|
776
874
|
|
|
777
875
|
/* Status dot on FAB */
|
|
778
876
|
self._statusDot = _el('span', { className: 'wakz-fab-dot ' + (self.config.online ? 'online' : 'offline') });
|
|
@@ -780,32 +878,41 @@
|
|
|
780
878
|
self._toggleBtn.appendChild(self._statusDot);
|
|
781
879
|
self._root.appendChild(self._toggleBtn);
|
|
782
880
|
|
|
783
|
-
/* ══════════════
|
|
784
|
-
self.
|
|
881
|
+
/* ══════════════ OVERLAY BACKDROP ══════════════ */
|
|
882
|
+
self._overlayEl = _el('div', { className: 'wakz-overlay' });
|
|
883
|
+
self._root.appendChild(self._overlayEl);
|
|
884
|
+
|
|
885
|
+
/* ══════════════ CHAT WINDOW (Centered Modal) ══════════════ */
|
|
886
|
+
self._chatWindow = _el('div', { className: 'wakz-window' });
|
|
785
887
|
|
|
786
888
|
/* ── Header ── */
|
|
787
889
|
var headerEl = _el('div', { className: 'wakz-hdr' });
|
|
788
890
|
|
|
789
|
-
|
|
891
|
+
/* Right side: avatar + name (in RTL, right side = start) */
|
|
892
|
+
var headerRight = _el('div', { className: 'wakz-hdr-right' });
|
|
790
893
|
var avatarLetter = (self.config.botName || 'W')[0].toUpperCase();
|
|
791
|
-
|
|
894
|
+
headerRight.appendChild(_el('div', { className: 'wakz-avatar' }, [avatarLetter]));
|
|
792
895
|
|
|
793
896
|
var headerInfo = _el('div', { className: 'wakz-hdr-info' });
|
|
794
897
|
headerInfo.appendChild(_el('span', { className: 'wakz-hdr-name' }, [self.config.botName]));
|
|
898
|
+
headerRight.appendChild(headerInfo);
|
|
899
|
+
headerEl.appendChild(headerRight);
|
|
900
|
+
|
|
901
|
+
/* Left side: status + close (in RTL, left side = end) */
|
|
902
|
+
var headerLeft = _el('div', { className: 'wakz-hdr-left' });
|
|
795
903
|
|
|
796
904
|
var statusLine = _el('span', { className: 'wakz-hdr-status' });
|
|
797
905
|
self._headerStatusDot = _el('span', { className: 'wakz-hdr-status-dot ' + (self.config.online ? 'online' : 'offline') });
|
|
798
906
|
if (self.config.showStatus) statusLine.appendChild(self._headerStatusDot);
|
|
799
907
|
self._headerStatusText = document.createTextNode(self.config.online ? str.online : str.offline);
|
|
800
908
|
statusLine.appendChild(self._headerStatusText);
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
headerLeft.appendChild(headerInfo);
|
|
804
|
-
headerEl.appendChild(headerLeft);
|
|
909
|
+
headerLeft.appendChild(statusLine);
|
|
805
910
|
|
|
806
911
|
var closeBtn = _el('button', { className: 'wakz-close', 'aria-label': str.closeChat });
|
|
807
912
|
closeBtn.innerHTML = _ICONS.close;
|
|
808
|
-
|
|
913
|
+
headerLeft.appendChild(closeBtn);
|
|
914
|
+
headerEl.appendChild(headerLeft);
|
|
915
|
+
|
|
809
916
|
self._chatWindow.appendChild(headerEl);
|
|
810
917
|
|
|
811
918
|
/* ── Messages Container ── */
|
|
@@ -851,6 +958,11 @@
|
|
|
851
958
|
self.toggleChat(false);
|
|
852
959
|
});
|
|
853
960
|
|
|
961
|
+
/* Overlay backdrop click to close */
|
|
962
|
+
self._overlayEl.addEventListener('click', function () {
|
|
963
|
+
self.toggleChat(false);
|
|
964
|
+
});
|
|
965
|
+
|
|
854
966
|
/* Send button click */
|
|
855
967
|
self._sendBtn.addEventListener('click', function () {
|
|
856
968
|
self._handleSend();
|
|
@@ -899,6 +1011,9 @@
|
|
|
899
1011
|
self.isOpen = open;
|
|
900
1012
|
if (open) {
|
|
901
1013
|
self._chatWindow.classList.add('wakz-visible');
|
|
1014
|
+
self._overlayEl.classList.add('wakz-visible');
|
|
1015
|
+
/* Hide FAB when chat is open */
|
|
1016
|
+
self._toggleBtn.classList.add('wakz-fab-hidden');
|
|
902
1017
|
/* Fetch history on first open (after config is loaded) */
|
|
903
1018
|
if (self._configLoaded && !self._hasFetchedHistory) {
|
|
904
1019
|
self._fetchHistory();
|
|
@@ -912,6 +1027,9 @@
|
|
|
912
1027
|
setTimeout(function () { self._inputEl.focus(); }, 320);
|
|
913
1028
|
} else {
|
|
914
1029
|
self._chatWindow.classList.remove('wakz-visible');
|
|
1030
|
+
self._overlayEl.classList.remove('wakz-visible');
|
|
1031
|
+
/* Show FAB when chat is closed */
|
|
1032
|
+
self._toggleBtn.classList.remove('wakz-fab-hidden');
|
|
915
1033
|
/* Stop polling when chat is closed */
|
|
916
1034
|
self._stopPolling();
|
|
917
1035
|
}
|
|
@@ -938,6 +1056,9 @@
|
|
|
938
1056
|
.then(function (data) {
|
|
939
1057
|
if (data && data.success && data.config) {
|
|
940
1058
|
self.config = Object.assign({}, _DEFAULTS, data.config);
|
|
1059
|
+
/* Apply script-level overrides */
|
|
1060
|
+
if (self._scriptIconColor) self.config.iconColor = self._scriptIconColor;
|
|
1061
|
+
if (self._scriptIconShape) self.config.iconShape = self._scriptIconShape;
|
|
941
1062
|
self._configLoaded = true;
|
|
942
1063
|
self._applyConfig();
|
|
943
1064
|
} else {
|
|
@@ -977,11 +1098,13 @@
|
|
|
977
1098
|
var str = _strings(cfg.language);
|
|
978
1099
|
var isRtl = cfg.language === 'ar';
|
|
979
1100
|
var posClass = cfg.position === 'bottom-left' ? 'bl' : 'br';
|
|
1101
|
+
var iconColor = cfg.iconColor || '#171717';
|
|
1102
|
+
var iconShape = cfg.iconShape || 'circle';
|
|
980
1103
|
|
|
981
1104
|
/* ── Update CSS custom properties ── */
|
|
982
1105
|
var hostStyle = self._shadow.host.style;
|
|
983
1106
|
hostStyle.setProperty('--wakz-primary', cfg.primaryColor);
|
|
984
|
-
hostStyle.setProperty('--wakz-
|
|
1107
|
+
hostStyle.setProperty('--wakz-icon-color', iconColor);
|
|
985
1108
|
hostStyle.setProperty('--wakz-chat-bg', cfg.chatBg);
|
|
986
1109
|
hostStyle.setProperty('--wakz-widget-bg', cfg.widgetBg);
|
|
987
1110
|
|
|
@@ -990,19 +1113,19 @@
|
|
|
990
1113
|
else self._root.classList.remove('wakz-rtl');
|
|
991
1114
|
|
|
992
1115
|
/* ── FAB ── */
|
|
993
|
-
|
|
1116
|
+
var fabClasses = 'wakz-fab wakz-fab-pos-' + posClass + ' wakz-fab-enter';
|
|
1117
|
+
if (iconShape === 'rounded') fabClasses += ' wakz-fab-shape-rounded';
|
|
1118
|
+
if (self.isOpen) fabClasses += ' wakz-fab-hidden';
|
|
1119
|
+
self._toggleBtn.className = fabClasses;
|
|
1120
|
+
self._toggleBtn.style.background = iconColor;
|
|
994
1121
|
self._toggleBtn.setAttribute('aria-label', str.openChat);
|
|
995
1122
|
|
|
996
1123
|
/* ── FAB Status Dot ── */
|
|
997
1124
|
self._statusDot.className = 'wakz-fab-dot ' + (cfg.online ? 'online' : 'offline');
|
|
998
1125
|
self._statusDot.style.display = cfg.showStatus ? '' : 'none';
|
|
999
1126
|
|
|
1000
|
-
/* ── Window
|
|
1001
|
-
self._chatWindow.className = 'wakz-window
|
|
1002
|
-
|
|
1003
|
-
/* ── Header ── */
|
|
1004
|
-
var headerEl = self._chatWindow.querySelector('.wakz-hdr');
|
|
1005
|
-
if (headerEl) headerEl.style.background = cfg.primaryColor;
|
|
1127
|
+
/* ── Window ── */
|
|
1128
|
+
self._chatWindow.className = 'wakz-window' + (self.isOpen ? ' wakz-visible' : '');
|
|
1006
1129
|
|
|
1007
1130
|
/* ── Bot Name & Avatar ── */
|
|
1008
1131
|
var nameEl = self._chatWindow.querySelector('.wakz-hdr-name');
|
|
@@ -1057,9 +1180,12 @@
|
|
|
1057
1180
|
if (data && data.success && data.messages && data.messages.length > 0) {
|
|
1058
1181
|
/* Clear any existing messages (e.g., welcome) and render history */
|
|
1059
1182
|
self._clearMessages();
|
|
1183
|
+
self._knownMessageIds = {};
|
|
1060
1184
|
var msgs = data.messages;
|
|
1061
1185
|
for (var i = 0; i < msgs.length; i++) {
|
|
1062
1186
|
var m = msgs[i];
|
|
1187
|
+
if (m.id && self._knownMessageIds[m.id]) continue;
|
|
1188
|
+
if (m.id) self._knownMessageIds[m.id] = true;
|
|
1063
1189
|
var role = m.role === 'user' ? 'user' : 'bot';
|
|
1064
1190
|
self._appendMessage(role, m.content, false, m.createdAt, m.role === 'human_agent');
|
|
1065
1191
|
}
|
|
@@ -1117,6 +1243,8 @@
|
|
|
1117
1243
|
var msgs = data.messages;
|
|
1118
1244
|
for (var i = 0; i < msgs.length; i++) {
|
|
1119
1245
|
var m = msgs[i];
|
|
1246
|
+
if (m.id && self._knownMessageIds[m.id]) continue;
|
|
1247
|
+
if (m.id) self._knownMessageIds[m.id] = true;
|
|
1120
1248
|
var role = m.role === 'user' ? 'user' : 'bot';
|
|
1121
1249
|
var isHumanAgent = m.role === 'human_agent';
|
|
1122
1250
|
self._appendMessage(role, m.content, false, m.createdAt, isHumanAgent);
|
|
@@ -1141,10 +1269,11 @@
|
|
|
1141
1269
|
var self = this;
|
|
1142
1270
|
self._messagesContainer.innerHTML = '';
|
|
1143
1271
|
self.messages = [];
|
|
1272
|
+
self._knownMessageIds = {};
|
|
1144
1273
|
};
|
|
1145
1274
|
|
|
1146
1275
|
/* ════════════════════════════════════════════════════════════════
|
|
1147
|
-
APPEND WELCOME MESSAGE (
|
|
1276
|
+
APPEND WELCOME MESSAGE (bot style — no bubble, flat on bg)
|
|
1148
1277
|
════════════════════════════════════════════════════════════════ */
|
|
1149
1278
|
|
|
1150
1279
|
WAKZWidget.prototype._appendWelcomeMessage = function (text) {
|
|
@@ -1154,13 +1283,13 @@
|
|
|
1154
1283
|
var wrap = _el('div', { className: 'wakz-welcome-wrap' });
|
|
1155
1284
|
|
|
1156
1285
|
/* Bot avatar */
|
|
1157
|
-
var avatar = _el('div', { className: 'wakz-
|
|
1286
|
+
var avatar = _el('div', { className: 'wakz-msg-bot-avatar' },
|
|
1158
1287
|
[(self.config.botName || 'W')[0].toUpperCase()]);
|
|
1159
1288
|
wrap.appendChild(avatar);
|
|
1160
1289
|
|
|
1161
|
-
/*
|
|
1162
|
-
var
|
|
1163
|
-
wrap.appendChild(
|
|
1290
|
+
/* Flat content with accent border */
|
|
1291
|
+
var content = _el('div', { className: 'wakz-msg-bot-content' }, [text]);
|
|
1292
|
+
wrap.appendChild(content);
|
|
1164
1293
|
|
|
1165
1294
|
self._messagesContainer.appendChild(wrap);
|
|
1166
1295
|
self.messages.push({ sender: 'bot', text: text });
|
|
@@ -1174,41 +1303,30 @@
|
|
|
1174
1303
|
WAKZWidget.prototype._appendMessage = function (sender, text, isError, timestamp, isHumanAgent) {
|
|
1175
1304
|
var self = this;
|
|
1176
1305
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
bubble.appendChild(document.createTextNode(text));
|
|
1306
|
+
if (sender === 'user') {
|
|
1307
|
+
/* ── User message: rounded bubble ── */
|
|
1308
|
+
var row = _el('div', { className: 'wakz-msg-row user' });
|
|
1309
|
+
var bubble = _el('div', { className: 'wakz-bubble-user' });
|
|
1310
|
+
bubble.appendChild(document.createTextNode(text));
|
|
1184
1311
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
try {
|
|
1189
|
-
var d = new Date(timestamp);
|
|
1190
|
-
if (!isNaN(d.getTime())) {
|
|
1191
|
-
tsText = d.getHours().toString().padStart(2, '0') + ':' +
|
|
1192
|
-
d.getMinutes().toString().padStart(2, '0');
|
|
1193
|
-
}
|
|
1194
|
-
} catch (e) { /* ignore */ }
|
|
1195
|
-
}
|
|
1196
|
-
if (!tsText) {
|
|
1197
|
-
var now = new Date();
|
|
1198
|
-
tsText = now.getHours().toString().padStart(2, '0') + ':' +
|
|
1199
|
-
now.getMinutes().toString().padStart(2, '0');
|
|
1200
|
-
}
|
|
1201
|
-
bubble.appendChild(_el('span', { className: 'wakz-ts' }, [tsText]));
|
|
1312
|
+
/* Timestamp */
|
|
1313
|
+
var tsText = self._formatTime(timestamp);
|
|
1314
|
+
bubble.appendChild(_el('span', { className: 'wakz-ts' }, [tsText]));
|
|
1202
1315
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1316
|
+
row.appendChild(bubble);
|
|
1317
|
+
self._messagesContainer.appendChild(row);
|
|
1318
|
+
self.messages.push({ sender: sender, text: text, isError: !!isError });
|
|
1319
|
+
self._scrollToBottom();
|
|
1320
|
+
return;
|
|
1208
1321
|
}
|
|
1209
1322
|
|
|
1210
|
-
/* Retry button for errors */
|
|
1211
1323
|
if (isError) {
|
|
1324
|
+
/* ── Error message: red-tinted bubble ── */
|
|
1325
|
+
var errRow = _el('div', { className: 'wakz-msg-row bot' });
|
|
1326
|
+
var errBubble = _el('div', { className: 'wakz-bubble-error' });
|
|
1327
|
+
errBubble.appendChild(document.createTextNode(text));
|
|
1328
|
+
|
|
1329
|
+
/* Retry button for errors */
|
|
1212
1330
|
var str = _strings(self.config.language);
|
|
1213
1331
|
var retryBtn = _el('button', { className: 'wakz-retry' }, [
|
|
1214
1332
|
_ICONS.error + ' ' + str.retry
|
|
@@ -1234,19 +1352,71 @@
|
|
|
1234
1352
|
}
|
|
1235
1353
|
if (lastUserMsg) self._sendToAPI(lastUserMsg);
|
|
1236
1354
|
});
|
|
1237
|
-
})(
|
|
1238
|
-
|
|
1355
|
+
})(errRow);
|
|
1356
|
+
errBubble.appendChild(retryBtn);
|
|
1357
|
+
|
|
1358
|
+
errRow.appendChild(errBubble);
|
|
1359
|
+
self._messagesContainer.appendChild(errRow);
|
|
1360
|
+
self.messages.push({ sender: sender, text: text, isError: true });
|
|
1361
|
+
self._scrollToBottom();
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/* ── Bot / Support message: flat on bg with accent border ── */
|
|
1366
|
+
var botRow = _el('div', { className: 'wakz-msg-row bot' });
|
|
1367
|
+
var wrap = _el('div', { className: 'wakz-msg-bot-wrap' });
|
|
1368
|
+
|
|
1369
|
+
/* Small bot avatar */
|
|
1370
|
+
var avatar = _el('div', { className: 'wakz-msg-bot-avatar' },
|
|
1371
|
+
[(self.config.botName || 'W')[0].toUpperCase()]);
|
|
1372
|
+
wrap.appendChild(avatar);
|
|
1373
|
+
|
|
1374
|
+
/* Flat content */
|
|
1375
|
+
var contentClass = 'wakz-msg-bot-content';
|
|
1376
|
+
if (isHumanAgent) contentClass += ' wakz-human';
|
|
1377
|
+
var content = _el('div', { className: contentClass });
|
|
1378
|
+
content.appendChild(document.createTextNode(text));
|
|
1379
|
+
|
|
1380
|
+
/* Timestamp */
|
|
1381
|
+
var botTs = self._formatTime(timestamp);
|
|
1382
|
+
content.appendChild(_el('span', { className: 'wakz-ts' }, [botTs]));
|
|
1383
|
+
|
|
1384
|
+
/* Human agent badge */
|
|
1385
|
+
if (isHumanAgent) {
|
|
1386
|
+
var iStr = _strings(self.config.language);
|
|
1387
|
+
var badge = _el('span', { className: 'wakz-human-badge' }, [iStr.humanBadge || '👤 فريق الدعم']);
|
|
1388
|
+
content.appendChild(badge);
|
|
1239
1389
|
}
|
|
1240
1390
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1391
|
+
wrap.appendChild(content);
|
|
1392
|
+
botRow.appendChild(wrap);
|
|
1393
|
+
self._messagesContainer.appendChild(botRow);
|
|
1243
1394
|
|
|
1244
|
-
self.messages.push({ sender: sender, text: text, isError: !!
|
|
1395
|
+
self.messages.push({ sender: sender, text: text, isError: false, isHumanAgent: !!isHumanAgent });
|
|
1245
1396
|
self._scrollToBottom();
|
|
1246
1397
|
};
|
|
1247
1398
|
|
|
1248
1399
|
/* ════════════════════════════════════════════════════════════════
|
|
1249
|
-
|
|
1400
|
+
FORMAT TIME
|
|
1401
|
+
════════════════════════════════════════════════════════════════ */
|
|
1402
|
+
|
|
1403
|
+
WAKZWidget.prototype._formatTime = function (timestamp) {
|
|
1404
|
+
if (timestamp) {
|
|
1405
|
+
try {
|
|
1406
|
+
var d = new Date(timestamp);
|
|
1407
|
+
if (!isNaN(d.getTime())) {
|
|
1408
|
+
return d.getHours().toString().padStart(2, '0') + ':' +
|
|
1409
|
+
d.getMinutes().toString().padStart(2, '0');
|
|
1410
|
+
}
|
|
1411
|
+
} catch (e) { /* ignore */ }
|
|
1412
|
+
}
|
|
1413
|
+
var now = new Date();
|
|
1414
|
+
return now.getHours().toString().padStart(2, '0') + ':' +
|
|
1415
|
+
now.getMinutes().toString().padStart(2, '0');
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1418
|
+
/* ════════════════════════════════════════════════════════════════
|
|
1419
|
+
TYPING INDICATOR (no bubble — on background)
|
|
1250
1420
|
════════════════════════════════════════════════════════════════ */
|
|
1251
1421
|
|
|
1252
1422
|
WAKZWidget.prototype._showTyping = function () {
|
|
@@ -1349,10 +1519,14 @@
|
|
|
1349
1519
|
} else {
|
|
1350
1520
|
self._appendMessage('bot', _strings(self.config.language).errorMsg, true);
|
|
1351
1521
|
}
|
|
1522
|
+
/* Update poll time to prevent re-fetching messages we already displayed */
|
|
1523
|
+
self._lastPollTime = new Date().toISOString();
|
|
1352
1524
|
})
|
|
1353
1525
|
.catch(function () {
|
|
1354
1526
|
self._hideTyping();
|
|
1355
1527
|
self._appendMessage('bot', _strings(self.config.language).errorMsg, true);
|
|
1528
|
+
/* Update poll time even on error to prevent re-fetching */
|
|
1529
|
+
self._lastPollTime = new Date().toISOString();
|
|
1356
1530
|
})
|
|
1357
1531
|
.finally(function () {
|
|
1358
1532
|
self.isLoading = false;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wakz-chat-widget",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Premium AI chat widget by WAKZ — Glassmorphism UI, Shadow DOM, zero deps, centered modal design.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"prepublishOnly": "echo 'Ready to publish wakz-chat-widget'"
|