@mobil80-dev/chatbot-widget 2.0.3 → 2.0.5

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/index.js ADDED
@@ -0,0 +1,301 @@
1
+ /* =========================================================
2
+ GLOBAL SINGLETON REFERENCES
3
+ ========================================================= */
4
+ let vcButton = null;
5
+ let vcCard = null;
6
+ let loaderEl = null;
7
+ let stylesInjected = false;
8
+ let isOpen = false;
9
+
10
+ /* =========================================================
11
+ STYLE INJECTION (ONCE)
12
+ ========================================================= */
13
+ function injectStylesOnce() {
14
+ if (stylesInjected) return
15
+ stylesInjected = true;
16
+
17
+ const style = document.createElement('style');
18
+ style.innerHTML = `
19
+ .vc-fab {
20
+ box-shadow: 0 8px 20px rgba(0,0,0,.25);
21
+ transition: box-shadow .2s ease, transform .2s ease;
22
+ }
23
+ .vc-fab:hover {
24
+ box-shadow: 0 12px 28px rgba(0,0,0,.3);
25
+ }
26
+ .vc-loader {
27
+ display: inline-flex;
28
+ gap: 6px;
29
+ padding: 8px 12px;
30
+ border-radius: 12px;
31
+ max-width: 80%;
32
+ margin-bottom: 8px;
33
+ }
34
+ .vc-loader span {
35
+ width: 6px;
36
+ height: 6px;
37
+ background: var(--vc-text-muted);
38
+ border-radius: 50%;
39
+ animation: vc-bounce 1.4s infinite ease-in-out both;
40
+ }
41
+ .vc-loader span:nth-child(1) { animation-delay: -0.32s; }
42
+ .vc-loader span:nth-child(2) { animation-delay: -0.16s; }
43
+ @keyframes vc-bounce {
44
+ 0%, 80%, 100% { transform: scale(0); }
45
+ 40% { transform: scale(1); }
46
+ }
47
+ `;
48
+ document.head.appendChild(style);
49
+ }
50
+
51
+ /* =========================================================
52
+ HELPER TO RESOLVE CONTAINER
53
+ ========================================================= */
54
+ function getAttachContainer(selector) {
55
+ if (!selector) return document.body
56
+ if (typeof selector === 'string') {
57
+ const el = document.querySelector(selector);
58
+ return el || document.body
59
+ }
60
+ return selector // accept DOM element directly
61
+ }
62
+
63
+ /* =========================================================
64
+ BUTTON
65
+ ========================================================= */
66
+ function getButton() {
67
+ if (vcButton) return vcButton
68
+ vcButton = document.createElement('div');
69
+ vcButton.className = 'vc-fab';
70
+ return vcButton
71
+ }
72
+
73
+ function updateButton(config) {
74
+ const button = getButton();
75
+ button.innerHTML = '';
76
+
77
+ const { buttonType = 'text', buttonContent = '🤖', buttonShape = 'circle', primaryColor = '#2563eb' } = config;
78
+ const isText = buttonType === 'text';
79
+
80
+ if (buttonType === 'image') {
81
+ const img = document.createElement('img');
82
+ img.src = buttonContent;
83
+ img.style.width = '26px';
84
+ img.style.height = '26px';
85
+ img.style.objectFit = 'contain';
86
+ button.appendChild(img);
87
+ } else {
88
+ button.textContent = buttonContent;
89
+ }
90
+
91
+ const borderRadius = buttonShape === 'pill' ? '999px' : buttonShape === 'square' ? '8px' : '50%';
92
+
93
+ Object.assign(button.style, {
94
+ position: 'fixed',
95
+ bottom: '20px',
96
+ right: '20px',
97
+ width: isText ? 'auto' : '56px',
98
+ height: isText ? 'auto' : '56px',
99
+ minWidth: '56px',
100
+ minHeight: '56px',
101
+ padding: isText ? '10px 16px' : '0',
102
+ borderRadius,
103
+ background: primaryColor,
104
+ color: '#fff',
105
+ display: 'flex',
106
+ alignItems: 'center',
107
+ justifyContent: 'center',
108
+ cursor: 'pointer',
109
+ fontSize: '18px',
110
+ fontWeight: '500',
111
+ userSelect: 'none',
112
+ zIndex: 999999
113
+ });
114
+ }
115
+
116
+ /* =========================================================
117
+ CARD
118
+ ========================================================= */
119
+ function getCard() {
120
+ if (vcCard) return vcCard
121
+ vcCard = document.createElement('div');
122
+ return vcCard
123
+ }
124
+
125
+ function updateCard(config) {
126
+ const card = getCard();
127
+
128
+ const theme = config.theme === 'dark' ? 'dark' : config.theme === 'light' ? 'light' :
129
+ window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
130
+ const isDark = theme === 'dark';
131
+
132
+ const colors = isDark
133
+ ? { cardBg: '#0f172a', headerBorder: '#1e293b', text: '#e5e7eb', mutedText: '#94a3b8', inputBg: '#020617', inputBorder: '#334155', botBubble: '#1e293b', userText: '#ffffff', messagesBg: '#020617' }
134
+ : { cardBg: '#ffffff', headerBorder: '#e5e7eb', text: '#0f172a', mutedText: '#64748b', inputBg: '#ffffff', inputBorder: '#cbd5f5', botBubble: '#e5e7eb', userText: '#ffffff', messagesBg: '#f8fafc' };
135
+
136
+ const primaryColor = config.primaryColor || '#2563eb';
137
+
138
+ card.innerHTML = `
139
+ <div style="padding:12px;border-bottom:1px solid ${colors.headerBorder};color:${colors.mutedText}">
140
+ Powered by <strong>VaultChat</strong>
141
+ </div>
142
+
143
+ <div id="vc-messages" style="
144
+ flex:1;
145
+ padding:12px;
146
+ overflow-y:auto;
147
+ background:${colors.messagesBg};
148
+ color:${colors.text}">
149
+ </div>
150
+
151
+ <div style="display:flex;padding:10px;border-top:1px solid ${colors.headerBorder};background:${colors.cardBg}">
152
+ <input id="vc-input" placeholder="Type your message..."
153
+ style="flex:1;padding:10px;border-radius:8px;border:1px solid ${colors.inputBorder};
154
+ background:${colors.inputBg};color:${colors.text}" />
155
+ <button id="vc-send" style="
156
+ margin-left:8px;padding:0 14px;border:none;border-radius:8px;
157
+ background:${primaryColor};color:white;cursor:pointer">➤</button>
158
+ </div>
159
+ `;
160
+
161
+ Object.assign(card.style, {
162
+ position: 'fixed',
163
+ bottom: '90px',
164
+ right: '20px',
165
+ width: '400px',
166
+ height: '420px',
167
+ background: colors.cardBg,
168
+ borderRadius: '12px',
169
+ boxShadow: '0 10px 30px rgba(0,0,0,.3)',
170
+ zIndex: 999999,
171
+ display: isOpen ? 'flex' : 'none',
172
+ flexDirection: 'column'
173
+ });
174
+
175
+ card.style.setProperty('--vc-text-muted', colors.mutedText);
176
+ card.style.setProperty('--vc-bot-bg', colors.botBubble);
177
+
178
+ const messages = card.querySelector('#vc-messages');
179
+ const input = card.querySelector('#vc-input');
180
+ const sendBtn = card.querySelector('#vc-send');
181
+
182
+ // send message
183
+ async function sendMessage() {
184
+ const text = input.value.trim();
185
+ if (!text) return
186
+
187
+ const userMsg = document.createElement('div');
188
+ userMsg.style.cssText = `
189
+ background:${primaryColor};
190
+ color:${colors.userText};
191
+ padding:8px 12px;
192
+ border-radius:12px;
193
+ max-width:80%;
194
+ margin:8px 0 8px auto`;
195
+ userMsg.textContent = text;
196
+ messages.appendChild(userMsg);
197
+
198
+ input.value = '';
199
+ messages.scrollTop = messages.scrollHeight;
200
+
201
+ if (!config.apiKey) {
202
+ addBotMessage('⚠️ API key not configured');
203
+ return
204
+ }
205
+
206
+ showLoader(messages, sendBtn);
207
+
208
+ try {
209
+ const res = await fetch('https://api.vaultchat.io/askChatbot', {
210
+ method: 'POST',
211
+ headers: { 'Content-Type': 'application/json' },
212
+ body: JSON.stringify({ api_key: config.apiKey, question: text })
213
+ });
214
+ const data = await res.json();
215
+ hideLoader(sendBtn);
216
+ addBotMessage(data?.data?.blocks?.map(b => b.text).join('\n') || 'No response');
217
+ } catch {
218
+ hideLoader(sendBtn);
219
+ addBotMessage('⚠️ Something went wrong');
220
+ }
221
+ }
222
+
223
+ function addBotMessage(text) {
224
+ const div = document.createElement('div');
225
+ div.style.cssText = `
226
+ background:${colors.botBubble};
227
+ color:${colors.text};
228
+ padding:8px 12px;
229
+ border-radius:12px;
230
+ max-width:80%;
231
+ margin-bottom:8px`;
232
+ div.textContent = text;
233
+ messages.appendChild(div);
234
+ messages.scrollTop = messages.scrollHeight;
235
+ }
236
+
237
+ // events
238
+ sendBtn.onclick = sendMessage;
239
+ input.addEventListener('keydown', e => { if (e.key === 'Enter') sendMessage(); });
240
+
241
+ return { messages, input, sendBtn }
242
+ }
243
+
244
+ /* =========================================================
245
+ LOADER
246
+ ========================================================= */
247
+ function showLoader(container, sendBtn) {
248
+ loaderEl = document.createElement('div');
249
+ loaderEl.className = 'vc-loader';
250
+ loaderEl.innerHTML = '<span></span><span></span><span></span>';
251
+ container.appendChild(loaderEl);
252
+
253
+ if (sendBtn) {
254
+ sendBtn.disabled = true;
255
+ sendBtn.style.opacity = '0.6';
256
+ sendBtn.style.cursor = 'not-allowed';
257
+ }
258
+ }
259
+
260
+ function hideLoader(sendBtn) {
261
+ loaderEl?.remove();
262
+ loaderEl = null;
263
+
264
+ if (sendBtn) {
265
+ sendBtn.disabled = false;
266
+ sendBtn.style.opacity = '1';
267
+ sendBtn.style.cursor = 'pointer';
268
+ }
269
+ }
270
+
271
+ /* =========================================================
272
+ PUBLIC API
273
+ ========================================================= */
274
+ const VaultChat = {
275
+ init(config = {}) {
276
+ injectStylesOnce();
277
+ updateButton(config);
278
+
279
+ const mountEl = getAttachContainer(config.attachToElement);
280
+ mountEl.appendChild(vcButton);
281
+
282
+ updateCard(config);
283
+ mountEl.appendChild(vcCard);
284
+
285
+ vcButton.onclick = () => {
286
+ isOpen = !isOpen;
287
+ vcCard.style.display = isOpen ? 'flex' : 'none';
288
+ };
289
+ },
290
+
291
+ destroy() {
292
+ vcButton?.remove();
293
+ vcCard?.remove();
294
+ vcButton = null;
295
+ vcCard = null;
296
+ loaderEl = null;
297
+ isOpen = false;
298
+ }
299
+ };
300
+
301
+ export { VaultChat as default };
@@ -0,0 +1,309 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
+ typeof define === 'function' && define.amd ? define(factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.VaultChat = factory());
5
+ })(this, (function () { 'use strict';
6
+
7
+ /* =========================================================
8
+ GLOBAL SINGLETON REFERENCES
9
+ ========================================================= */
10
+ let vcButton = null;
11
+ let vcCard = null;
12
+ let loaderEl = null;
13
+ let stylesInjected = false;
14
+ let isOpen = false;
15
+
16
+ /* =========================================================
17
+ STYLE INJECTION (ONCE)
18
+ ========================================================= */
19
+ function injectStylesOnce() {
20
+ if (stylesInjected) return
21
+ stylesInjected = true;
22
+
23
+ const style = document.createElement('style');
24
+ style.innerHTML = `
25
+ .vc-fab {
26
+ box-shadow: 0 8px 20px rgba(0,0,0,.25);
27
+ transition: box-shadow .2s ease, transform .2s ease;
28
+ }
29
+ .vc-fab:hover {
30
+ box-shadow: 0 12px 28px rgba(0,0,0,.3);
31
+ }
32
+ .vc-loader {
33
+ display: inline-flex;
34
+ gap: 6px;
35
+ padding: 8px 12px;
36
+ border-radius: 12px;
37
+ max-width: 80%;
38
+ margin-bottom: 8px;
39
+ }
40
+ .vc-loader span {
41
+ width: 6px;
42
+ height: 6px;
43
+ background: var(--vc-text-muted);
44
+ border-radius: 50%;
45
+ animation: vc-bounce 1.4s infinite ease-in-out both;
46
+ }
47
+ .vc-loader span:nth-child(1) { animation-delay: -0.32s; }
48
+ .vc-loader span:nth-child(2) { animation-delay: -0.16s; }
49
+ @keyframes vc-bounce {
50
+ 0%, 80%, 100% { transform: scale(0); }
51
+ 40% { transform: scale(1); }
52
+ }
53
+ `;
54
+ document.head.appendChild(style);
55
+ }
56
+
57
+ /* =========================================================
58
+ HELPER TO RESOLVE CONTAINER
59
+ ========================================================= */
60
+ function getAttachContainer(selector) {
61
+ if (!selector) return document.body
62
+ if (typeof selector === 'string') {
63
+ const el = document.querySelector(selector);
64
+ return el || document.body
65
+ }
66
+ return selector // accept DOM element directly
67
+ }
68
+
69
+ /* =========================================================
70
+ BUTTON
71
+ ========================================================= */
72
+ function getButton() {
73
+ if (vcButton) return vcButton
74
+ vcButton = document.createElement('div');
75
+ vcButton.className = 'vc-fab';
76
+ return vcButton
77
+ }
78
+
79
+ function updateButton(config) {
80
+ const button = getButton();
81
+ button.innerHTML = '';
82
+
83
+ const { buttonType = 'text', buttonContent = '🤖', buttonShape = 'circle', primaryColor = '#2563eb' } = config;
84
+ const isText = buttonType === 'text';
85
+
86
+ if (buttonType === 'image') {
87
+ const img = document.createElement('img');
88
+ img.src = buttonContent;
89
+ img.style.width = '26px';
90
+ img.style.height = '26px';
91
+ img.style.objectFit = 'contain';
92
+ button.appendChild(img);
93
+ } else {
94
+ button.textContent = buttonContent;
95
+ }
96
+
97
+ const borderRadius = buttonShape === 'pill' ? '999px' : buttonShape === 'square' ? '8px' : '50%';
98
+
99
+ Object.assign(button.style, {
100
+ position: 'fixed',
101
+ bottom: '20px',
102
+ right: '20px',
103
+ width: isText ? 'auto' : '56px',
104
+ height: isText ? 'auto' : '56px',
105
+ minWidth: '56px',
106
+ minHeight: '56px',
107
+ padding: isText ? '10px 16px' : '0',
108
+ borderRadius,
109
+ background: primaryColor,
110
+ color: '#fff',
111
+ display: 'flex',
112
+ alignItems: 'center',
113
+ justifyContent: 'center',
114
+ cursor: 'pointer',
115
+ fontSize: '18px',
116
+ fontWeight: '500',
117
+ userSelect: 'none',
118
+ zIndex: 999999
119
+ });
120
+ }
121
+
122
+ /* =========================================================
123
+ CARD
124
+ ========================================================= */
125
+ function getCard() {
126
+ if (vcCard) return vcCard
127
+ vcCard = document.createElement('div');
128
+ return vcCard
129
+ }
130
+
131
+ function updateCard(config) {
132
+ const card = getCard();
133
+
134
+ const theme = config.theme === 'dark' ? 'dark' : config.theme === 'light' ? 'light' :
135
+ window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
136
+ const isDark = theme === 'dark';
137
+
138
+ const colors = isDark
139
+ ? { cardBg: '#0f172a', headerBorder: '#1e293b', text: '#e5e7eb', mutedText: '#94a3b8', inputBg: '#020617', inputBorder: '#334155', botBubble: '#1e293b', userText: '#ffffff', messagesBg: '#020617' }
140
+ : { cardBg: '#ffffff', headerBorder: '#e5e7eb', text: '#0f172a', mutedText: '#64748b', inputBg: '#ffffff', inputBorder: '#cbd5f5', botBubble: '#e5e7eb', userText: '#ffffff', messagesBg: '#f8fafc' };
141
+
142
+ const primaryColor = config.primaryColor || '#2563eb';
143
+
144
+ card.innerHTML = `
145
+ <div style="padding:12px;border-bottom:1px solid ${colors.headerBorder};color:${colors.mutedText}">
146
+ Powered by <strong>VaultChat</strong>
147
+ </div>
148
+
149
+ <div id="vc-messages" style="
150
+ flex:1;
151
+ padding:12px;
152
+ overflow-y:auto;
153
+ background:${colors.messagesBg};
154
+ color:${colors.text}">
155
+ </div>
156
+
157
+ <div style="display:flex;padding:10px;border-top:1px solid ${colors.headerBorder};background:${colors.cardBg}">
158
+ <input id="vc-input" placeholder="Type your message..."
159
+ style="flex:1;padding:10px;border-radius:8px;border:1px solid ${colors.inputBorder};
160
+ background:${colors.inputBg};color:${colors.text}" />
161
+ <button id="vc-send" style="
162
+ margin-left:8px;padding:0 14px;border:none;border-radius:8px;
163
+ background:${primaryColor};color:white;cursor:pointer">➤</button>
164
+ </div>
165
+ `;
166
+
167
+ Object.assign(card.style, {
168
+ position: 'fixed',
169
+ bottom: '90px',
170
+ right: '20px',
171
+ width: '400px',
172
+ height: '420px',
173
+ background: colors.cardBg,
174
+ borderRadius: '12px',
175
+ boxShadow: '0 10px 30px rgba(0,0,0,.3)',
176
+ zIndex: 999999,
177
+ display: isOpen ? 'flex' : 'none',
178
+ flexDirection: 'column'
179
+ });
180
+
181
+ card.style.setProperty('--vc-text-muted', colors.mutedText);
182
+ card.style.setProperty('--vc-bot-bg', colors.botBubble);
183
+
184
+ const messages = card.querySelector('#vc-messages');
185
+ const input = card.querySelector('#vc-input');
186
+ const sendBtn = card.querySelector('#vc-send');
187
+
188
+ // send message
189
+ async function sendMessage() {
190
+ const text = input.value.trim();
191
+ if (!text) return
192
+
193
+ const userMsg = document.createElement('div');
194
+ userMsg.style.cssText = `
195
+ background:${primaryColor};
196
+ color:${colors.userText};
197
+ padding:8px 12px;
198
+ border-radius:12px;
199
+ max-width:80%;
200
+ margin:8px 0 8px auto`;
201
+ userMsg.textContent = text;
202
+ messages.appendChild(userMsg);
203
+
204
+ input.value = '';
205
+ messages.scrollTop = messages.scrollHeight;
206
+
207
+ if (!config.apiKey) {
208
+ addBotMessage('⚠️ API key not configured');
209
+ return
210
+ }
211
+
212
+ showLoader(messages, sendBtn);
213
+
214
+ try {
215
+ const res = await fetch('https://api.vaultchat.io/askChatbot', {
216
+ method: 'POST',
217
+ headers: { 'Content-Type': 'application/json' },
218
+ body: JSON.stringify({ api_key: config.apiKey, question: text })
219
+ });
220
+ const data = await res.json();
221
+ hideLoader(sendBtn);
222
+ addBotMessage(data?.data?.blocks?.map(b => b.text).join('\n') || 'No response');
223
+ } catch {
224
+ hideLoader(sendBtn);
225
+ addBotMessage('⚠️ Something went wrong');
226
+ }
227
+ }
228
+
229
+ function addBotMessage(text) {
230
+ const div = document.createElement('div');
231
+ div.style.cssText = `
232
+ background:${colors.botBubble};
233
+ color:${colors.text};
234
+ padding:8px 12px;
235
+ border-radius:12px;
236
+ max-width:80%;
237
+ margin-bottom:8px`;
238
+ div.textContent = text;
239
+ messages.appendChild(div);
240
+ messages.scrollTop = messages.scrollHeight;
241
+ }
242
+
243
+ // events
244
+ sendBtn.onclick = sendMessage;
245
+ input.addEventListener('keydown', e => { if (e.key === 'Enter') sendMessage(); });
246
+
247
+ return { messages, input, sendBtn }
248
+ }
249
+
250
+ /* =========================================================
251
+ LOADER
252
+ ========================================================= */
253
+ function showLoader(container, sendBtn) {
254
+ loaderEl = document.createElement('div');
255
+ loaderEl.className = 'vc-loader';
256
+ loaderEl.innerHTML = '<span></span><span></span><span></span>';
257
+ container.appendChild(loaderEl);
258
+
259
+ if (sendBtn) {
260
+ sendBtn.disabled = true;
261
+ sendBtn.style.opacity = '0.6';
262
+ sendBtn.style.cursor = 'not-allowed';
263
+ }
264
+ }
265
+
266
+ function hideLoader(sendBtn) {
267
+ loaderEl?.remove();
268
+ loaderEl = null;
269
+
270
+ if (sendBtn) {
271
+ sendBtn.disabled = false;
272
+ sendBtn.style.opacity = '1';
273
+ sendBtn.style.cursor = 'pointer';
274
+ }
275
+ }
276
+
277
+ /* =========================================================
278
+ PUBLIC API
279
+ ========================================================= */
280
+ const VaultChat = {
281
+ init(config = {}) {
282
+ injectStylesOnce();
283
+ updateButton(config);
284
+
285
+ const mountEl = getAttachContainer(config.attachToElement);
286
+ mountEl.appendChild(vcButton);
287
+
288
+ updateCard(config);
289
+ mountEl.appendChild(vcCard);
290
+
291
+ vcButton.onclick = () => {
292
+ isOpen = !isOpen;
293
+ vcCard.style.display = isOpen ? 'flex' : 'none';
294
+ };
295
+ },
296
+
297
+ destroy() {
298
+ vcButton?.remove();
299
+ vcCard?.remove();
300
+ vcButton = null;
301
+ vcCard = null;
302
+ loaderEl = null;
303
+ isOpen = false;
304
+ }
305
+ };
306
+
307
+ return VaultChat;
308
+
309
+ }));
package/index.js CHANGED
@@ -10,7 +10,7 @@ let isOpen = false
10
10
  /* =========================================================
11
11
  STYLE INJECTION (ONCE)
12
12
  ========================================================= */
13
- function injectStylesOnce() {
13
+ function injectStylesOnce () {
14
14
  if (stylesInjected) return
15
15
  stylesInjected = true
16
16
 
@@ -51,7 +51,7 @@ function injectStylesOnce() {
51
51
  /* =========================================================
52
52
  HELPER TO RESOLVE CONTAINER
53
53
  ========================================================= */
54
- function getAttachContainer(selector) {
54
+ function getAttachContainer (selector) {
55
55
  if (!selector) return document.body
56
56
  if (typeof selector === 'string') {
57
57
  const el = document.querySelector(selector)
@@ -63,18 +63,23 @@ function getAttachContainer(selector) {
63
63
  /* =========================================================
64
64
  BUTTON
65
65
  ========================================================= */
66
- function getButton() {
66
+ function getButton () {
67
67
  if (vcButton) return vcButton
68
68
  vcButton = document.createElement('div')
69
69
  vcButton.className = 'vc-fab'
70
70
  return vcButton
71
71
  }
72
72
 
73
- function updateButton(config) {
73
+ function updateButton (config) {
74
74
  const button = getButton()
75
75
  button.innerHTML = ''
76
76
 
77
- const { buttonType = 'text', buttonContent = '🤖', buttonShape = 'circle', primaryColor = '#2563eb' } = config
77
+ const {
78
+ buttonType = 'text',
79
+ buttonContent = '🤖',
80
+ buttonShape = 'circle',
81
+ primaryColor = '#2563eb'
82
+ } = config
78
83
  const isText = buttonType === 'text'
79
84
 
80
85
  if (buttonType === 'image') {
@@ -88,7 +93,8 @@ function updateButton(config) {
88
93
  button.textContent = buttonContent
89
94
  }
90
95
 
91
- const borderRadius = buttonShape === 'pill' ? '999px' : buttonShape === 'square' ? '8px' : '50%'
96
+ const borderRadius =
97
+ buttonShape === 'pill' ? '999px' : buttonShape === 'square' ? '8px' : '50%'
92
98
 
93
99
  Object.assign(button.style, {
94
100
  position: 'fixed',
@@ -116,22 +122,48 @@ function updateButton(config) {
116
122
  /* =========================================================
117
123
  CARD
118
124
  ========================================================= */
119
- function getCard() {
125
+ function getCard () {
120
126
  if (vcCard) return vcCard
121
127
  vcCard = document.createElement('div')
122
128
  return vcCard
123
129
  }
124
130
 
125
- function updateCard(config) {
131
+ function updateCard (config) {
126
132
  const card = getCard()
127
133
 
128
- const theme = config.theme === 'dark' ? 'dark' : config.theme === 'light' ? 'light' :
129
- window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
134
+ const theme =
135
+ config.theme === 'dark'
136
+ ? 'dark'
137
+ : config.theme === 'light'
138
+ ? 'light'
139
+ : window.matchMedia('(prefers-color-scheme: dark)').matches
140
+ ? 'dark'
141
+ : 'light'
130
142
  const isDark = theme === 'dark'
131
143
 
132
144
  const colors = isDark
133
- ? { cardBg: '#0f172a', headerBorder: '#1e293b', text: '#e5e7eb', mutedText: '#94a3b8', inputBg: '#020617', inputBorder: '#334155', botBubble: '#1e293b', userText: '#ffffff', messagesBg: '#020617' }
134
- : { cardBg: '#ffffff', headerBorder: '#e5e7eb', text: '#0f172a', mutedText: '#64748b', inputBg: '#ffffff', inputBorder: '#cbd5f5', botBubble: '#e5e7eb', userText: '#ffffff', messagesBg: '#f8fafc' }
145
+ ? {
146
+ cardBg: '#0f172a',
147
+ headerBorder: '#1e293b',
148
+ text: '#e5e7eb',
149
+ mutedText: '#94a3b8',
150
+ inputBg: '#020617',
151
+ inputBorder: '#334155',
152
+ botBubble: '#1e293b',
153
+ userText: '#ffffff',
154
+ messagesBg: '#020617'
155
+ }
156
+ : {
157
+ cardBg: '#ffffff',
158
+ headerBorder: '#e5e7eb',
159
+ text: '#0f172a',
160
+ mutedText: '#64748b',
161
+ inputBg: '#ffffff',
162
+ inputBorder: '#cbd5f5',
163
+ botBubble: '#e5e7eb',
164
+ userText: '#ffffff',
165
+ messagesBg: '#f8fafc'
166
+ }
135
167
 
136
168
  const primaryColor = config.primaryColor || '#2563eb'
137
169
 
@@ -162,8 +194,10 @@ function updateCard(config) {
162
194
  position: 'fixed',
163
195
  bottom: '90px',
164
196
  right: '20px',
197
+ maxWidth: '90vw',
165
198
  width: '400px',
166
199
  height: '420px',
200
+ maxHeight: '80vh',
167
201
  background: colors.cardBg,
168
202
  borderRadius: '12px',
169
203
  boxShadow: '0 10px 30px rgba(0,0,0,.3)',
@@ -171,6 +205,10 @@ function updateCard(config) {
171
205
  display: isOpen ? 'flex' : 'none',
172
206
  flexDirection: 'column'
173
207
  })
208
+ if (window.innerWidth < 450) {
209
+ card.style.bottom = '10px'
210
+ card.style.right = '5px'
211
+ }
174
212
 
175
213
  card.style.setProperty('--vc-text-muted', colors.mutedText)
176
214
  card.style.setProperty('--vc-bot-bg', colors.botBubble)
@@ -180,7 +218,7 @@ function updateCard(config) {
180
218
  const sendBtn = card.querySelector('#vc-send')
181
219
 
182
220
  // send message
183
- async function sendMessage() {
221
+ async function sendMessage () {
184
222
  const text = input.value.trim()
185
223
  if (!text) return
186
224
 
@@ -213,14 +251,16 @@ function updateCard(config) {
213
251
  })
214
252
  const data = await res.json()
215
253
  hideLoader(sendBtn)
216
- addBotMessage(data?.data?.blocks?.map(b => b.text).join('\n') || 'No response')
254
+ addBotMessage(
255
+ data?.data?.blocks?.map(b => b.text).join('\n') || 'No response'
256
+ )
217
257
  } catch {
218
258
  hideLoader(sendBtn)
219
259
  addBotMessage('⚠️ Something went wrong')
220
260
  }
221
261
  }
222
262
 
223
- function addBotMessage(text) {
263
+ function addBotMessage (text) {
224
264
  const div = document.createElement('div')
225
265
  div.style.cssText = `
226
266
  background:${colors.botBubble};
@@ -236,7 +276,9 @@ function updateCard(config) {
236
276
 
237
277
  // events
238
278
  sendBtn.onclick = sendMessage
239
- input.addEventListener('keydown', e => { if (e.key === 'Enter') sendMessage() })
279
+ input.addEventListener('keydown', e => {
280
+ if (e.key === 'Enter') sendMessage()
281
+ })
240
282
 
241
283
  return { messages, input, sendBtn }
242
284
  }
@@ -244,7 +286,7 @@ function updateCard(config) {
244
286
  /* =========================================================
245
287
  LOADER
246
288
  ========================================================= */
247
- function showLoader(container, sendBtn) {
289
+ function showLoader (container, sendBtn) {
248
290
  loaderEl = document.createElement('div')
249
291
  loaderEl.className = 'vc-loader'
250
292
  loaderEl.innerHTML = '<span></span><span></span><span></span>'
@@ -257,7 +299,7 @@ function showLoader(container, sendBtn) {
257
299
  }
258
300
  }
259
301
 
260
- function hideLoader(sendBtn) {
302
+ function hideLoader (sendBtn) {
261
303
  loaderEl?.remove()
262
304
  loaderEl = null
263
305
 
@@ -272,7 +314,7 @@ function hideLoader(sendBtn) {
272
314
  PUBLIC API
273
315
  ========================================================= */
274
316
  const VaultChat = {
275
- init(config = {}) {
317
+ init (config = {}) {
276
318
  injectStylesOnce()
277
319
  updateButton(config)
278
320
 
@@ -288,7 +330,7 @@ const VaultChat = {
288
330
  }
289
331
  },
290
332
 
291
- destroy() {
333
+ destroy () {
292
334
  vcButton?.remove()
293
335
  vcCard?.remove()
294
336
  vcButton = null
package/package.json CHANGED
@@ -1,9 +1,17 @@
1
1
  {
2
2
  "name": "@mobil80-dev/chatbot-widget",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "Drop-in JavaScript chat widget for websites (no iframe, no framework)",
5
- "main": "index.js",
5
+ "author": "NanthaGopal",
6
+ "license": "MIT",
7
+
8
+ "main": "dist/index.js",
9
+ "module": "dist/index.js",
10
+ "unpkg": "dist/index.umd.js",
11
+ "jsdelivr": "dist/index.umd.js",
12
+
6
13
  "type": "module",
14
+
7
15
  "keywords": [
8
16
  "chat",
9
17
  "chatbot",
@@ -11,9 +19,12 @@
11
19
  "javascript",
12
20
  "vaultchat"
13
21
  ],
14
- "author": "NanthaGopal",
15
- "license": "MIT",
16
- "dependencies": {
17
- "@mobil80-dev/chatbot-widget": "^1.0.8"
22
+
23
+ "scripts": {
24
+ "build": "rollup -c"
25
+ },
26
+
27
+ "devDependencies": {
28
+ "rollup": "^4.54.0"
18
29
  }
19
30
  }
@@ -0,0 +1,14 @@
1
+ export default {
2
+ input: 'index.js',
3
+ output: [
4
+ {
5
+ file: 'dist/index.js',
6
+ format: 'es'
7
+ },
8
+ {
9
+ file: 'dist/index.umd.js',
10
+ format: 'umd',
11
+ name: 'VaultChat'
12
+ }
13
+ ]
14
+ }