@mobil80-dev/chatbot-widget 2.0.1 → 2.0.3

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.
Files changed (3) hide show
  1. package/README.md +31 -9
  2. package/index.js +69 -84
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -11,6 +11,7 @@ A lightweight JavaScript chatbot widget powered by VaultChat, designed to be emb
11
11
  - No iframe
12
12
  - No dependencies
13
13
  - Easy setup
14
+ - Optional page/container restriction via `attachToElement`
14
15
 
15
16
  ## 📦 Installation
16
17
 
@@ -112,20 +113,41 @@ VaultChat.init({
112
113
 
113
114
  ```
114
115
 
116
+ ## 📌 attachToElement (Optional)
117
+
118
+ VaultChat allows you to specify a container where the chat widget should be rendered. By default, the chat button and card are appended to document.body.
119
+
120
+ You can restrict the widget to a specific element by passing a CSS selector or a DOM element using the attachToElement option:
121
+
122
+ ```
123
+ // Using a CSS selector
124
+ VaultChat.init({
125
+ apiKey: 'YOUR_API_KEY',
126
+ attachToElement: '#dashboard-container'
127
+ })
128
+
129
+ // Using a DOM element
130
+ const container = document.getElementById('dashboard-container')
131
+ VaultChat.init({
132
+ apiKey: 'YOUR_API_KEY',
133
+ attachToElement: container
134
+ })
135
+ ```
136
+
115
137
  📌 Call this inside ngOnInit() of your root or layout component.
116
138
 
117
139
  ## ⚙️ Configuration Reference
118
140
 
119
141
  ```
120
- | Option | Type | Description |
121
- | ------------- | ------ | ----------------------------------- |
122
- | apiKey | string | Your VaultChat API key |
123
- | primaryColor | string | Primary UI color |
124
- | theme | string | `light` or `dark` |
125
- | buttonContent | string | text, image URL, |
126
- | buttonType | string | `text` | `image` |
127
- | buttonShape | string | `circle` | `square` | `pill` |
128
-
142
+ | Option | Type | Description |
143
+ | --------------- | ---------- | ----------------------------------------------- |
144
+ | apiKey | string | Your VaultChat API key |
145
+ | primaryColor | string | Primary UI color |
146
+ | theme | string | `light` or `dark` |
147
+ | buttonContent | string | Text, image URL, or emoji |
148
+ | buttonType | string | `text` or `image` |
149
+ | buttonShape | string | `circle`, `square`, or `pill` |
150
+ | attachToElement | string/DOM | CSS selector or DOM element to mount the widget |
129
151
 
130
152
  ```
131
153
 
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
 
@@ -23,7 +23,6 @@ function injectStylesOnce () {
23
23
  .vc-fab:hover {
24
24
  box-shadow: 0 12px 28px rgba(0,0,0,.3);
25
25
  }
26
-
27
26
  .vc-loader {
28
27
  display: inline-flex;
29
28
  gap: 6px;
@@ -41,7 +40,6 @@ function injectStylesOnce () {
41
40
  }
42
41
  .vc-loader span:nth-child(1) { animation-delay: -0.32s; }
43
42
  .vc-loader span:nth-child(2) { animation-delay: -0.16s; }
44
-
45
43
  @keyframes vc-bounce {
46
44
  0%, 80%, 100% { transform: scale(0); }
47
45
  40% { transform: scale(1); }
@@ -50,30 +48,33 @@ function injectStylesOnce () {
50
48
  document.head.appendChild(style)
51
49
  }
52
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
+
53
63
  /* =========================================================
54
64
  BUTTON
55
65
  ========================================================= */
56
- function getButton () {
66
+ function getButton() {
57
67
  if (vcButton) return vcButton
58
-
59
68
  vcButton = document.createElement('div')
60
69
  vcButton.className = 'vc-fab'
61
- document.body.appendChild(vcButton)
62
-
63
70
  return vcButton
64
71
  }
65
72
 
66
- function updateButton (config) {
73
+ function updateButton(config) {
67
74
  const button = getButton()
68
75
  button.innerHTML = ''
69
76
 
70
- const {
71
- buttonType = 'text',
72
- buttonContent = 'Chat',
73
- buttonShape = 'circle',
74
- primaryColor = '#2563eb'
75
- } = config
76
-
77
+ const { buttonType = 'text', buttonContent = '🤖', buttonShape = 'circle', primaryColor = '#2563eb' } = config
77
78
  const isText = buttonType === 'text'
78
79
 
79
80
  if (buttonType === 'image') {
@@ -87,8 +88,7 @@ function updateButton (config) {
87
88
  button.textContent = buttonContent
88
89
  }
89
90
 
90
- const borderRadius =
91
- buttonShape === 'pill' ? '999px' : buttonShape === 'square' ? '8px' : '50%'
91
+ const borderRadius = buttonShape === 'pill' ? '999px' : buttonShape === 'square' ? '8px' : '50%'
92
92
 
93
93
  Object.assign(button.style, {
94
94
  position: 'fixed',
@@ -116,51 +116,22 @@ function updateButton (config) {
116
116
  /* =========================================================
117
117
  CARD
118
118
  ========================================================= */
119
- function getCard () {
119
+ function getCard() {
120
120
  if (vcCard) return vcCard
121
-
122
121
  vcCard = document.createElement('div')
123
- document.body.appendChild(vcCard)
124
122
  return vcCard
125
123
  }
126
124
 
127
- function updateCard (config) {
125
+ function updateCard(config) {
128
126
  const card = getCard()
129
127
 
130
- const theme =
131
- config.theme === 'dark'
132
- ? 'dark'
133
- : config.theme === 'light'
134
- ? 'light'
135
- : window.matchMedia('(prefers-color-scheme: dark)').matches
136
- ? 'dark'
137
- : 'light'
138
-
128
+ const theme = config.theme === 'dark' ? 'dark' : config.theme === 'light' ? 'light' :
129
+ window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
139
130
  const isDark = theme === 'dark'
140
131
 
141
132
  const colors = isDark
142
- ? {
143
- cardBg: '#0f172a',
144
- headerBorder: '#1e293b',
145
- text: '#e5e7eb',
146
- mutedText: '#94a3b8',
147
- inputBg: '#020617',
148
- inputBorder: '#334155',
149
- botBubble: '#1e293b',
150
- userText: '#ffffff',
151
- messagesBg: '#020617'
152
- }
153
- : {
154
- cardBg: '#ffffff',
155
- headerBorder: '#e5e7eb',
156
- text: '#0f172a',
157
- mutedText: '#64748b',
158
- inputBg: '#ffffff',
159
- inputBorder: '#cbd5f5',
160
- botBubble: '#e5e7eb',
161
- userText: '#ffffff',
162
- messagesBg: '#f8fafc'
163
- }
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' }
164
135
 
165
136
  const primaryColor = config.primaryColor || '#2563eb'
166
137
 
@@ -208,20 +179,22 @@ function updateCard (config) {
208
179
  const input = card.querySelector('#vc-input')
209
180
  const sendBtn = card.querySelector('#vc-send')
210
181
 
211
- function sendMessage () {
182
+ // send message
183
+ async function sendMessage() {
212
184
  const text = input.value.trim()
213
185
  if (!text) return
214
186
 
215
187
  const userMsg = document.createElement('div')
216
188
  userMsg.style.cssText = `
217
- background:${config.primaryColor || '#2563eb'};
218
- color:${config.theme === 'dark' ? '#fff' : '#fff'};
219
- padding:8px 12px;
220
- border-radius:12px;
221
- max-width:80%;
222
- margin:8px 0 8px auto`
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`
223
195
  userMsg.textContent = text
224
196
  messages.appendChild(userMsg)
197
+
225
198
  input.value = ''
226
199
  messages.scrollTop = messages.scrollHeight
227
200
 
@@ -232,29 +205,22 @@ function updateCard (config) {
232
205
 
233
206
  showLoader(messages, sendBtn)
234
207
 
235
- fetch('https://api.vaultchat.io/askChatbot', {
236
- method: 'POST',
237
- headers: { 'Content-Type': 'application/json' },
238
- body: JSON.stringify({ api_key: config.apiKey, question: text })
239
- })
240
- .then(res => res.json())
241
- .then(data => {
242
- hideLoader(sendBtn)
243
- addBotMessage(
244
- data?.data?.blocks?.map(b => b.text).join('\n') || 'No response'
245
- )
246
- })
247
- .catch(() => {
248
- hideLoader(sendBtn)
249
- addBotMessage('⚠️ Something went wrong')
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 })
250
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
+ }
251
221
  }
252
- sendBtn.onclick = sendMessage
253
- input.addEventListener('keydown', e => {
254
- if (e.key === 'Enter') sendMessage()
255
- })
256
222
 
257
- function addBotMessage (text) {
223
+ function addBotMessage(text) {
258
224
  const div = document.createElement('div')
259
225
  div.style.cssText = `
260
226
  background:${colors.botBubble};
@@ -267,12 +233,18 @@ function updateCard (config) {
267
233
  messages.appendChild(div)
268
234
  messages.scrollTop = messages.scrollHeight
269
235
  }
236
+
237
+ // events
238
+ sendBtn.onclick = sendMessage
239
+ input.addEventListener('keydown', e => { if (e.key === 'Enter') sendMessage() })
240
+
241
+ return { messages, input, sendBtn }
270
242
  }
271
243
 
272
244
  /* =========================================================
273
245
  LOADER
274
246
  ========================================================= */
275
- function showLoader (container, sendBtn) {
247
+ function showLoader(container, sendBtn) {
276
248
  loaderEl = document.createElement('div')
277
249
  loaderEl.className = 'vc-loader'
278
250
  loaderEl.innerHTML = '<span></span><span></span><span></span>'
@@ -285,7 +257,7 @@ function showLoader (container, sendBtn) {
285
257
  }
286
258
  }
287
259
 
288
- function hideLoader (sendBtn) {
260
+ function hideLoader(sendBtn) {
289
261
  loaderEl?.remove()
290
262
  loaderEl = null
291
263
 
@@ -300,16 +272,29 @@ function hideLoader (sendBtn) {
300
272
  PUBLIC API
301
273
  ========================================================= */
302
274
  const VaultChat = {
303
- init (config = {}) {
275
+ init(config = {}) {
304
276
  injectStylesOnce()
305
277
  updateButton(config)
278
+
279
+ const mountEl = getAttachContainer(config.attachToElement)
280
+ mountEl.appendChild(vcButton)
281
+
306
282
  updateCard(config)
283
+ mountEl.appendChild(vcCard)
307
284
 
308
- const button = getButton()
309
- button.onclick = () => {
285
+ vcButton.onclick = () => {
310
286
  isOpen = !isOpen
311
287
  vcCard.style.display = isOpen ? 'flex' : 'none'
312
288
  }
289
+ },
290
+
291
+ destroy() {
292
+ vcButton?.remove()
293
+ vcCard?.remove()
294
+ vcButton = null
295
+ vcCard = null
296
+ loaderEl = null
297
+ isOpen = false
313
298
  }
314
299
  }
315
300
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobil80-dev/chatbot-widget",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "Drop-in JavaScript chat widget for websites (no iframe, no framework)",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -11,7 +11,7 @@
11
11
  "javascript",
12
12
  "vaultchat"
13
13
  ],
14
- "author": "Nantha Gopal",
14
+ "author": "NanthaGopal",
15
15
  "license": "MIT",
16
16
  "dependencies": {
17
17
  "@mobil80-dev/chatbot-widget": "^1.0.8"