@bubblav/ai-chatbot-angular 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # @bubblav/ai-chatbot-angular
2
+
3
+ Angular component for embedding the BubblaV AI chatbot widget in your Angular application.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @bubblav/ai-chatbot-angular
9
+ # or
10
+ yarn add @bubblav/ai-chatbot-angular
11
+ # or
12
+ pnpm add @bubblav/ai-chatbot-angular
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Import the Module (Angular 14+ - Standalone)
18
+
19
+ Since this is a standalone component, you can import it directly in your component:
20
+
21
+ ```ts
22
+ import { Component } from '@angular/core';
23
+ import { BubblaVWidgetComponent } from '@bubblav/ai-chatbot-angular';
24
+
25
+ @Component({
26
+ selector: 'app-root',
27
+ standalone: true,
28
+ imports: [BubblaVWidgetComponent],
29
+ template: `
30
+ <bubblav-widget
31
+ websiteId="your-website-id"
32
+ />
33
+ `
34
+ })
35
+ export class AppComponent {}
36
+ ```
37
+
38
+ ### Basic Usage
39
+
40
+ ```html
41
+ <bubblav-widget
42
+ websiteId="your-website-id">
43
+ </bubblav-widget>
44
+ ```
45
+
46
+ ### With Custom Styling
47
+
48
+ ```html
49
+ <bubblav-widget
50
+ websiteId="your-website-id"
51
+ bubbleColor="#3b82f6"
52
+ bubbleIconColor="#ffffff"
53
+ desktopPosition="bottom-right"
54
+ mobilePosition="bottom-right">
55
+ </bubblav-widget>
56
+ ```
57
+
58
+ ### Dynamic Configuration
59
+
60
+ ```ts
61
+ import { Component } from '@angular/core';
62
+ import { BubblaVWidgetComponent } from '@bubblav/ai-chatbot-angular';
63
+
64
+ @Component({
65
+ selector: 'app-root',
66
+ standalone: true,
67
+ imports: [BubblaVWidgetComponent],
68
+ template: `
69
+ <bubblav-widget
70
+ [websiteId]="websiteId"
71
+ [bubbleColor]="bubbleColor">
72
+ </bubblav-widget>
73
+ `
74
+ })
75
+ export class AppComponent {
76
+ websiteId = 'your-website-id';
77
+ bubbleColor = '#3b82f6';
78
+ }
79
+ ```
80
+
81
+ ### Using the Service
82
+
83
+ For programmatic access to the widget SDK, inject the service:
84
+
85
+ ```ts
86
+ import { Component, inject } from '@angular/core';
87
+ import { BubblaVWidgetService } from '@bubblav/ai-chatbot-angular';
88
+
89
+ @Component({
90
+ selector: 'app-support',
91
+ template: `
92
+ <button (click)="openChat()">Open Chat</button>
93
+ <button (click)="sendMessage()">Send Help Message</button>
94
+ `
95
+ })
96
+ export class SupportComponent {
97
+ private bubblav = inject(BubblaVWidgetService);
98
+
99
+ openChat() {
100
+ this.bubblav.open();
101
+ }
102
+
103
+ sendMessage() {
104
+ this.bubblav.sendMessage('I need help with my order');
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### With Service Initialization
110
+
111
+ If you want more control, you can initialize the widget manually through the service:
112
+
113
+ ```ts
114
+ import { Component, inject, OnInit } from '@angular/core';
115
+ import { BubblaVWidgetService } from '@bubblav/ai-chatbot-angular';
116
+
117
+ @Component({
118
+ selector: 'app-root',
119
+ template: `
120
+ <h1>My App</h1>
121
+ `
122
+ })
123
+ export class AppComponent implements OnInit {
124
+ private bubblav = inject(BubblaVWidgetService);
125
+
126
+ ngOnInit() {
127
+ this.bubblav.initialize({
128
+ websiteId: 'your-website-id',
129
+ bubbleColor: '#3b82f6',
130
+ desktopPosition: 'bottom-right'
131
+ });
132
+ }
133
+
134
+ openChat() {
135
+ this.bubblav.open();
136
+ }
137
+ }
138
+ ```
139
+
140
+ ### Observable State
141
+
142
+ Subscribe to widget state changes:
143
+
144
+ ```ts
145
+ import { Component, inject } from '@angular/core';
146
+ import { BubblaVWidgetService } from '@bubblav/ai-chatbot-angular';
147
+
148
+ @Component({
149
+ selector: 'app-widget-status',
150
+ template: `
151
+ <div>Widget is {{ isOpen$ | async ? 'open' : 'closed' }}</div>
152
+ `
153
+ })
154
+ export class WidgetStatusComponent {
155
+ private bubblav = inject(BubblaVWidgetService);
156
+ isOpen$ = this.bubblav.isOpen$;
157
+ }
158
+ ```
159
+
160
+ ## Inputs
161
+
162
+ | Input | Type | Required | Default | Description |
163
+ |-------|------|----------|---------|-------------|
164
+ | `websiteId` | `string` | Yes | - | Your website ID from the BubblaV dashboard |
165
+ | `apiUrl` | `string` | No | Production API | Custom API URL (for self-hosted deployments) |
166
+ | `bubbleColor` | `string` | No | - | Bubble button color (hex) |
167
+ | `bubbleIconColor` | `string` | No | - | Bubble icon color (hex) |
168
+ | `desktopPosition` | `'bottom-left' \| 'bottom-right'` | No | `'bottom-right'` | Desktop position |
169
+ | `mobilePosition` | `'bottom-left' \| 'bottom-right'` | No | `'bottom-right'` | Mobile position |
170
+ | `poweredByVisible` | `boolean` | No | `true` | Show/hide powered by branding |
171
+ | `botName` | `string` | No | `'Bot'` | Custom bot name |
172
+ | `greetingMessage` | `string` | No | - | Greeting message when widget opens |
173
+ | `textboxPlaceholder` | `string` | No | - | Input placeholder text |
174
+ | `showActionButtons` | `boolean` | No | `true` | Show/hide action buttons |
175
+
176
+ ## Service Methods
177
+
178
+ | Method | Description |
179
+ |--------|-------------|
180
+ | `initialize(config)` | Initialize the widget with configuration |
181
+ | `open()` | Open the widget |
182
+ | `close()` | Close the widget |
183
+ | `toggle()` | Toggle widget open/close |
184
+ | `isOpen()` | Check if widget is open |
185
+ | `sendMessage(text, conversationId?)` | Send a message programmatically |
186
+ | `showGreeting(message?)` | Show greeting message |
187
+ | `hideGreeting()` | Hide greeting message |
188
+ | `getConfig()` | Get current widget configuration |
189
+ | `setDebug(enabled)` | Enable/disable debug mode |
190
+ | `on(event, callback)` | Register event listener |
191
+ | `off(event, callback)` | Unregister event listener |
192
+ | `emit(event, data?)` | Emit event |
193
+ | `ready(callback)` | Ready callback for widget loaded |
194
+ | `track(eventName, properties?)` | Track analytics event |
195
+
196
+ ## Service Observables
197
+
198
+ | Observable | Description |
199
+ |------------|-------------|
200
+ | `isOpen$` | Emits `true` when widget opens, `false` when closes |
201
+
202
+ ## Getting Your Website ID
203
+
204
+ 1. Go to [bubblav.com/dashboard](https://www.bubblav.com/dashboard)
205
+ 2. Select your website
206
+ 3. Go to **Installation**
207
+ 4. Copy your website ID
208
+
209
+ ## Server-Side Rendering (SSR)
210
+
211
+ This component is SSR-safe. The widget script only loads in the browser.
212
+
213
+ ## Angular Versions
214
+
215
+ Compatible with Angular 14+ (supports standalone components).
216
+
217
+ ## TypeScript
218
+
219
+ This package is written in TypeScript and includes full type definitions.
220
+
221
+ ## License
222
+
223
+ MIT
224
+
225
+ ## Support
226
+
227
+ - Documentation: [docs.bubblav.com](https://docs.bubblav.com)
228
+ - Issues: [GitHub Issues](https://github.com/tonnguyen/botcanchat/issues)
package/dist/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # @bubblav/ai-chatbot-angular
2
+
3
+ Angular component for embedding the BubblaV AI chatbot widget in your Angular application.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @bubblav/ai-chatbot-angular
9
+ # or
10
+ yarn add @bubblav/ai-chatbot-angular
11
+ # or
12
+ pnpm add @bubblav/ai-chatbot-angular
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Import the Module (Angular 14+ - Standalone)
18
+
19
+ Since this is a standalone component, you can import it directly in your component:
20
+
21
+ ```ts
22
+ import { Component } from '@angular/core';
23
+ import { BubblaVWidgetComponent } from '@bubblav/ai-chatbot-angular';
24
+
25
+ @Component({
26
+ selector: 'app-root',
27
+ standalone: true,
28
+ imports: [BubblaVWidgetComponent],
29
+ template: `
30
+ <bubblav-widget
31
+ websiteId="your-website-id"
32
+ />
33
+ `
34
+ })
35
+ export class AppComponent {}
36
+ ```
37
+
38
+ ### Basic Usage
39
+
40
+ ```html
41
+ <bubblav-widget
42
+ websiteId="your-website-id">
43
+ </bubblav-widget>
44
+ ```
45
+
46
+ ### With Custom Styling
47
+
48
+ ```html
49
+ <bubblav-widget
50
+ websiteId="your-website-id"
51
+ bubbleColor="#3b82f6"
52
+ bubbleIconColor="#ffffff"
53
+ desktopPosition="bottom-right"
54
+ mobilePosition="bottom-right">
55
+ </bubblav-widget>
56
+ ```
57
+
58
+ ### Dynamic Configuration
59
+
60
+ ```ts
61
+ import { Component } from '@angular/core';
62
+ import { BubblaVWidgetComponent } from '@bubblav/ai-chatbot-angular';
63
+
64
+ @Component({
65
+ selector: 'app-root',
66
+ standalone: true,
67
+ imports: [BubblaVWidgetComponent],
68
+ template: `
69
+ <bubblav-widget
70
+ [websiteId]="websiteId"
71
+ [bubbleColor]="bubbleColor">
72
+ </bubblav-widget>
73
+ `
74
+ })
75
+ export class AppComponent {
76
+ websiteId = 'your-website-id';
77
+ bubbleColor = '#3b82f6';
78
+ }
79
+ ```
80
+
81
+ ### Using the Service
82
+
83
+ For programmatic access to the widget SDK, inject the service:
84
+
85
+ ```ts
86
+ import { Component, inject } from '@angular/core';
87
+ import { BubblaVWidgetService } from '@bubblav/ai-chatbot-angular';
88
+
89
+ @Component({
90
+ selector: 'app-support',
91
+ template: `
92
+ <button (click)="openChat()">Open Chat</button>
93
+ <button (click)="sendMessage()">Send Help Message</button>
94
+ `
95
+ })
96
+ export class SupportComponent {
97
+ private bubblav = inject(BubblaVWidgetService);
98
+
99
+ openChat() {
100
+ this.bubblav.open();
101
+ }
102
+
103
+ sendMessage() {
104
+ this.bubblav.sendMessage('I need help with my order');
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### With Service Initialization
110
+
111
+ If you want more control, you can initialize the widget manually through the service:
112
+
113
+ ```ts
114
+ import { Component, inject, OnInit } from '@angular/core';
115
+ import { BubblaVWidgetService } from '@bubblav/ai-chatbot-angular';
116
+
117
+ @Component({
118
+ selector: 'app-root',
119
+ template: `
120
+ <h1>My App</h1>
121
+ `
122
+ })
123
+ export class AppComponent implements OnInit {
124
+ private bubblav = inject(BubblaVWidgetService);
125
+
126
+ ngOnInit() {
127
+ this.bubblav.initialize({
128
+ websiteId: 'your-website-id',
129
+ bubbleColor: '#3b82f6',
130
+ desktopPosition: 'bottom-right'
131
+ });
132
+ }
133
+
134
+ openChat() {
135
+ this.bubblav.open();
136
+ }
137
+ }
138
+ ```
139
+
140
+ ### Observable State
141
+
142
+ Subscribe to widget state changes:
143
+
144
+ ```ts
145
+ import { Component, inject } from '@angular/core';
146
+ import { BubblaVWidgetService } from '@bubblav/ai-chatbot-angular';
147
+
148
+ @Component({
149
+ selector: 'app-widget-status',
150
+ template: `
151
+ <div>Widget is {{ isOpen$ | async ? 'open' : 'closed' }}</div>
152
+ `
153
+ })
154
+ export class WidgetStatusComponent {
155
+ private bubblav = inject(BubblaVWidgetService);
156
+ isOpen$ = this.bubblav.isOpen$;
157
+ }
158
+ ```
159
+
160
+ ## Inputs
161
+
162
+ | Input | Type | Required | Default | Description |
163
+ |-------|------|----------|---------|-------------|
164
+ | `websiteId` | `string` | Yes | - | Your website ID from the BubblaV dashboard |
165
+ | `apiUrl` | `string` | No | Production API | Custom API URL (for self-hosted deployments) |
166
+ | `bubbleColor` | `string` | No | - | Bubble button color (hex) |
167
+ | `bubbleIconColor` | `string` | No | - | Bubble icon color (hex) |
168
+ | `desktopPosition` | `'bottom-left' \| 'bottom-right'` | No | `'bottom-right'` | Desktop position |
169
+ | `mobilePosition` | `'bottom-left' \| 'bottom-right'` | No | `'bottom-right'` | Mobile position |
170
+ | `poweredByVisible` | `boolean` | No | `true` | Show/hide powered by branding |
171
+ | `botName` | `string` | No | `'Bot'` | Custom bot name |
172
+ | `greetingMessage` | `string` | No | - | Greeting message when widget opens |
173
+ | `textboxPlaceholder` | `string` | No | - | Input placeholder text |
174
+ | `showActionButtons` | `boolean` | No | `true` | Show/hide action buttons |
175
+
176
+ ## Service Methods
177
+
178
+ | Method | Description |
179
+ |--------|-------------|
180
+ | `initialize(config)` | Initialize the widget with configuration |
181
+ | `open()` | Open the widget |
182
+ | `close()` | Close the widget |
183
+ | `toggle()` | Toggle widget open/close |
184
+ | `isOpen()` | Check if widget is open |
185
+ | `sendMessage(text, conversationId?)` | Send a message programmatically |
186
+ | `showGreeting(message?)` | Show greeting message |
187
+ | `hideGreeting()` | Hide greeting message |
188
+ | `getConfig()` | Get current widget configuration |
189
+ | `setDebug(enabled)` | Enable/disable debug mode |
190
+ | `on(event, callback)` | Register event listener |
191
+ | `off(event, callback)` | Unregister event listener |
192
+ | `emit(event, data?)` | Emit event |
193
+ | `ready(callback)` | Ready callback for widget loaded |
194
+ | `track(eventName, properties?)` | Track analytics event |
195
+
196
+ ## Service Observables
197
+
198
+ | Observable | Description |
199
+ |------------|-------------|
200
+ | `isOpen$` | Emits `true` when widget opens, `false` when closes |
201
+
202
+ ## Getting Your Website ID
203
+
204
+ 1. Go to [bubblav.com/dashboard](https://www.bubblav.com/dashboard)
205
+ 2. Select your website
206
+ 3. Go to **Installation**
207
+ 4. Copy your website ID
208
+
209
+ ## Server-Side Rendering (SSR)
210
+
211
+ This component is SSR-safe. The widget script only loads in the browser.
212
+
213
+ ## Angular Versions
214
+
215
+ Compatible with Angular 14+ (supports standalone components).
216
+
217
+ ## TypeScript
218
+
219
+ This package is written in TypeScript and includes full type definitions.
220
+
221
+ ## License
222
+
223
+ MIT
224
+
225
+ ## Support
226
+
227
+ - Documentation: [docs.bubblav.com](https://docs.bubblav.com)
228
+ - Issues: [GitHub Issues](https://github.com/tonnguyen/botcanchat/issues)
@@ -0,0 +1,383 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, NgZone, DestroyRef, Injectable, ElementRef, Input, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { BehaviorSubject } from 'rxjs';
4
+
5
+ /**
6
+ * Utility functions for @bubblav/ai-chatbot-angular
7
+ */
8
+ const WIDGET_SCRIPT_URL = 'https://www.bubblav.com/widget.js';
9
+ const DEFAULT_API_URL = 'https://www.bubblav.com/api/chat';
10
+ /**
11
+ * Convert camelCase props to data-* attribute names
12
+ */
13
+ function propsToDataAttributes(props) {
14
+ const dataAttrs = {};
15
+ for (const [key, value] of Object.entries(props)) {
16
+ if (value === undefined || value === null)
17
+ continue;
18
+ // Convert camelCase to kebab-case for data attributes
19
+ const dataKey = 'data-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();
20
+ dataAttrs[dataKey] = String(value);
21
+ }
22
+ return dataAttrs;
23
+ }
24
+ /**
25
+ * Get the widget script URL
26
+ */
27
+ function getWidgetScriptUrl(apiUrl) {
28
+ if (typeof window === 'undefined') {
29
+ return WIDGET_SCRIPT_URL;
30
+ }
31
+ // If custom API URL provided, derive widget URL from it
32
+ if (apiUrl) {
33
+ try {
34
+ const url = new URL(apiUrl, window.location.origin);
35
+ // Replace /api/chat with /widget.js
36
+ return url.origin + '/widget.js';
37
+ }
38
+ catch {
39
+ return WIDGET_SCRIPT_URL;
40
+ }
41
+ }
42
+ return WIDGET_SCRIPT_URL;
43
+ }
44
+ /**
45
+ * Validate website ID format
46
+ */
47
+ function validateWebsiteId(websiteId) {
48
+ // Basic validation: should be a non-empty string
49
+ // Format varies, so we just check it's a reasonable string
50
+ return typeof websiteId === 'string' && websiteId.length > 0 && websiteId.length < 100;
51
+ }
52
+ /**
53
+ * Filter config to only include widget config (exclude websiteId)
54
+ */
55
+ function getConfigProps(config) {
56
+ const { websiteId: _websiteId, ...configProps } = config;
57
+ return configProps;
58
+ }
59
+ /**
60
+ * Check if we're in a browser environment
61
+ */
62
+ function isBrowser() {
63
+ return typeof window !== 'undefined' && typeof document !== 'undefined';
64
+ }
65
+ /**
66
+ * Check if a widget script is already loaded
67
+ */
68
+ function isScriptLoaded(url) {
69
+ if (!isBrowser())
70
+ return false;
71
+ const scripts = document.getElementsByTagName('script');
72
+ for (let i = 0; i < scripts.length; i++) {
73
+ if (scripts[i].src === url) {
74
+ return true;
75
+ }
76
+ }
77
+ return false;
78
+ }
79
+
80
+ /**
81
+ * BubblaVWidget Service
82
+ *
83
+ * Injectable service for programmatic access to the BubblaV SDK.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * constructor(private bubblav: BubblaVWidgetService) {
88
+ * bubblav.open();
89
+ * bubblav.sendMessage('Hello');
90
+ * }
91
+ * ```
92
+ */
93
+ class BubblaVWidgetService {
94
+ ngZone = inject(NgZone);
95
+ destroyRef = inject(DestroyRef);
96
+ sdk = null;
97
+ scriptElement = null;
98
+ isInitialized = false;
99
+ // Track widget state
100
+ isOpenSubject = new BehaviorSubject(false);
101
+ isOpen$ = this.isOpenSubject.asObservable();
102
+ constructor() {
103
+ // Initialize SDK if already available
104
+ if (isBrowser() && window.BubblaV) {
105
+ this.sdk = window.BubblaV;
106
+ this.setupEventListeners();
107
+ }
108
+ }
109
+ /**
110
+ * Initialize the widget with configuration
111
+ */
112
+ initialize(config) {
113
+ if (!isBrowser()) {
114
+ return;
115
+ }
116
+ if (this.isInitialized) {
117
+ console.warn('[BubblaV] Widget already initialized');
118
+ return;
119
+ }
120
+ // Validate website ID
121
+ if (!validateWebsiteId(config.websiteId)) {
122
+ console.warn(`[BubblaV] Invalid website ID format: "${config.websiteId}". ` +
123
+ `Please check your website ID in the BubblaV dashboard.`);
124
+ return;
125
+ }
126
+ // Get script URL
127
+ const scriptUrl = getWidgetScriptUrl(config.apiUrl);
128
+ // Check if script is already loaded
129
+ if (isScriptLoaded(scriptUrl)) {
130
+ console.warn(`[BubblaV] Widget script already loaded. ` +
131
+ `Only one widget instance should be active.`);
132
+ if (window.BubblaV) {
133
+ this.sdk = window.BubblaV;
134
+ this.setupEventListeners();
135
+ }
136
+ return;
137
+ }
138
+ // Mark as initialized
139
+ this.isInitialized = true;
140
+ // Create script element
141
+ const script = document.createElement('script');
142
+ script.src = scriptUrl;
143
+ script.async = true;
144
+ script.defer = true;
145
+ // Set data attributes
146
+ script.setAttribute('data-site-id', config.websiteId);
147
+ // Set optional config as data attributes
148
+ const configProps = getConfigProps(config);
149
+ const dataAttrs = propsToDataAttributes(configProps);
150
+ for (const [key, value] of Object.entries(dataAttrs)) {
151
+ script.setAttribute(key, value);
152
+ }
153
+ // Handle load event
154
+ script.onload = () => {
155
+ this.ngZone.run(() => {
156
+ if (window.BubblaV) {
157
+ this.sdk = window.BubblaV;
158
+ this.setupEventListeners();
159
+ }
160
+ });
161
+ };
162
+ // Handle error event
163
+ script.onerror = () => {
164
+ console.error(`[BubblaV] Failed to load widget script from ${scriptUrl}. ` +
165
+ `Please check your network connection and ensure the URL is correct.`);
166
+ this.isInitialized = false;
167
+ };
168
+ // Add script to document
169
+ document.body.appendChild(script);
170
+ this.scriptElement = script;
171
+ }
172
+ /**
173
+ * Setup event listeners for widget state changes
174
+ */
175
+ setupEventListeners() {
176
+ if (!this.sdk)
177
+ return;
178
+ this.sdk.on('widget_opened', () => {
179
+ this.ngZone.run(() => {
180
+ this.isOpenSubject.next(true);
181
+ });
182
+ });
183
+ this.sdk.on('widget_closed', () => {
184
+ this.ngZone.run(() => {
185
+ this.isOpenSubject.next(false);
186
+ });
187
+ });
188
+ }
189
+ /**
190
+ * Cleanup widget
191
+ */
192
+ destroy() {
193
+ if (this.scriptElement && this.scriptElement.parentNode) {
194
+ this.scriptElement.parentNode.removeChild(this.scriptElement);
195
+ }
196
+ this.scriptElement = null;
197
+ this.sdk = null;
198
+ this.isInitialized = false;
199
+ }
200
+ // SDK Methods
201
+ open() {
202
+ this.sdk?.open();
203
+ }
204
+ close() {
205
+ this.sdk?.close();
206
+ }
207
+ toggle() {
208
+ this.sdk?.toggle();
209
+ }
210
+ isOpen() {
211
+ return this.sdk?.isOpen() ?? false;
212
+ }
213
+ sendMessage(text, conversationId) {
214
+ this.sdk?.sendMessage(text, conversationId);
215
+ }
216
+ showGreeting(message) {
217
+ this.sdk?.showGreeting(message);
218
+ }
219
+ hideGreeting() {
220
+ this.sdk?.hideGreeting();
221
+ }
222
+ getConfig() {
223
+ return this.sdk?.getConfig() ?? {};
224
+ }
225
+ setDebug(enabled) {
226
+ this.sdk?.setDebug(enabled);
227
+ }
228
+ // Event methods
229
+ /**
230
+ * Listen to widget events
231
+ */
232
+ on(event, callback) {
233
+ this.sdk?.on(event, callback);
234
+ }
235
+ /**
236
+ * Unregister event listener
237
+ */
238
+ off(event, callback) {
239
+ this.sdk?.off(event, callback);
240
+ }
241
+ /**
242
+ * Emit event
243
+ */
244
+ emit(event, data) {
245
+ this.sdk?.emit(event, data);
246
+ }
247
+ /**
248
+ * Ready callback for widget loaded
249
+ */
250
+ ready(callback) {
251
+ this.sdk?.ready(callback);
252
+ }
253
+ /**
254
+ * Track analytics event
255
+ */
256
+ track(eventName, properties) {
257
+ this.sdk?.track(eventName, properties);
258
+ }
259
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: BubblaVWidgetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
260
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: BubblaVWidgetService, providedIn: 'root' });
261
+ }
262
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: BubblaVWidgetService, decorators: [{
263
+ type: Injectable,
264
+ args: [{
265
+ providedIn: 'root',
266
+ }]
267
+ }], ctorParameters: () => [] });
268
+
269
+ /**
270
+ * BubblaVWidget Component
271
+ *
272
+ * Standalone Angular component for embedding the BubblaV chat widget.
273
+ *
274
+ * @example
275
+ * ```html
276
+ * <bubblav-widget
277
+ * websiteId="your-website-id"
278
+ * bubbleColor="#3b82f6"
279
+ * desktopPosition="bottom-right">
280
+ * </bubblav-widget>
281
+ * ```
282
+ */
283
+ class BubblaVWidgetComponent {
284
+ service = inject(BubblaVWidgetService);
285
+ elementRef = inject(ElementRef);
286
+ // Required input
287
+ websiteId;
288
+ // Optional styling inputs
289
+ apiUrl;
290
+ bubbleColor;
291
+ bubbleIconColor;
292
+ desktopPosition;
293
+ mobilePosition;
294
+ poweredByVisible;
295
+ botName;
296
+ greetingMessage;
297
+ textboxPlaceholder;
298
+ showActionButtons;
299
+ config = {
300
+ websiteId: '',
301
+ };
302
+ ngOnInit() {
303
+ // Validate website ID
304
+ if (!validateWebsiteId(this.websiteId)) {
305
+ console.warn(`[BubblaV] Invalid website ID format: "${this.websiteId}". ` +
306
+ `Please check your website ID in the BubblaV dashboard.`);
307
+ return;
308
+ }
309
+ // Build config object
310
+ this.config = {
311
+ websiteId: this.websiteId,
312
+ ...(this.apiUrl !== undefined && { apiUrl: this.apiUrl }),
313
+ ...(this.bubbleColor !== undefined && { bubbleColor: this.bubbleColor }),
314
+ ...(this.bubbleIconColor !== undefined && { bubbleIconColor: this.bubbleIconColor }),
315
+ ...(this.desktopPosition !== undefined && { desktopPosition: this.desktopPosition }),
316
+ ...(this.mobilePosition !== undefined && { mobilePosition: this.mobilePosition }),
317
+ ...(this.poweredByVisible !== undefined && { poweredByVisible: this.poweredByVisible }),
318
+ ...(this.botName !== undefined && { botName: this.botName }),
319
+ ...(this.greetingMessage !== undefined && { greetingMessage: this.greetingMessage }),
320
+ ...(this.textboxPlaceholder !== undefined && { textboxPlaceholder: this.textboxPlaceholder }),
321
+ ...(this.showActionButtons !== undefined && { showActionButtons: this.showActionButtons }),
322
+ };
323
+ // Initialize widget
324
+ this.service.initialize(this.config);
325
+ }
326
+ ngOnDestroy() {
327
+ // Cleanup widget
328
+ this.service.destroy();
329
+ }
330
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: BubblaVWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
331
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.5", type: BubblaVWidgetComponent, isStandalone: true, selector: "bubblav-widget", inputs: { websiteId: "websiteId", apiUrl: "apiUrl", bubbleColor: "bubbleColor", bubbleIconColor: "bubbleIconColor", desktopPosition: "desktopPosition", mobilePosition: "mobilePosition", poweredByVisible: "poweredByVisible", botName: "botName", greetingMessage: "greetingMessage", textboxPlaceholder: "textboxPlaceholder", showActionButtons: "showActionButtons" }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
332
+ }
333
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: BubblaVWidgetComponent, decorators: [{
334
+ type: Component,
335
+ args: [{
336
+ selector: 'bubblav-widget',
337
+ template: '',
338
+ standalone: true,
339
+ changeDetection: ChangeDetectionStrategy.OnPush,
340
+ }]
341
+ }], propDecorators: { websiteId: [{
342
+ type: Input,
343
+ args: [{ required: true }]
344
+ }], apiUrl: [{
345
+ type: Input
346
+ }], bubbleColor: [{
347
+ type: Input
348
+ }], bubbleIconColor: [{
349
+ type: Input
350
+ }], desktopPosition: [{
351
+ type: Input
352
+ }], mobilePosition: [{
353
+ type: Input
354
+ }], poweredByVisible: [{
355
+ type: Input
356
+ }], botName: [{
357
+ type: Input
358
+ }], greetingMessage: [{
359
+ type: Input
360
+ }], textboxPlaceholder: [{
361
+ type: Input
362
+ }], showActionButtons: [{
363
+ type: Input
364
+ }] } });
365
+
366
+ /**
367
+ * @bubblav/ai-chatbot-angular
368
+ *
369
+ * Angular component and service for embedding the BubblaV AI chatbot widget.
370
+ *
371
+ * @example
372
+ * ```ts
373
+ * import { BubblaVWidgetComponent } from '@bubblav/ai-chatbot-angular';
374
+ * ```
375
+ */
376
+ // Component export
377
+
378
+ /**
379
+ * Generated bundle index. Do not edit.
380
+ */
381
+
382
+ export { BubblaVWidgetComponent, BubblaVWidgetService, getConfigProps, getWidgetScriptUrl, isBrowser, isScriptLoaded, propsToDataAttributes, validateWebsiteId };
383
+ //# sourceMappingURL=bubblav-ai-chatbot-angular.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bubblav-ai-chatbot-angular.mjs","sources":["../../src/lib/utils.ts","../../src/lib/bubblav-widget.service.ts","../../src/lib/bubblav-widget.component.ts","../../src/index.ts","../../src/bubblav-ai-chatbot-angular.ts"],"sourcesContent":["/**\n * Utility functions for @bubblav/ai-chatbot-angular\n */\n\nimport { BubblaVWidgetConfig } from './types';\n\nconst WIDGET_SCRIPT_URL = 'https://www.bubblav.com/widget.js';\nconst DEFAULT_API_URL = 'https://www.bubblav.com/api/chat';\n\n/**\n * Convert camelCase props to data-* attribute names\n */\nexport function propsToDataAttributes(props: Record<string, unknown>): Record<string, string> {\n const dataAttrs: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(props)) {\n if (value === undefined || value === null) continue;\n\n // Convert camelCase to kebab-case for data attributes\n const dataKey = 'data-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();\n dataAttrs[dataKey] = String(value);\n }\n\n return dataAttrs;\n}\n\n/**\n * Get the widget script URL\n */\nexport function getWidgetScriptUrl(apiUrl?: string): string {\n if (typeof window === 'undefined') {\n return WIDGET_SCRIPT_URL;\n }\n\n // If custom API URL provided, derive widget URL from it\n if (apiUrl) {\n try {\n const url = new URL(apiUrl, window.location.origin);\n // Replace /api/chat with /widget.js\n return url.origin + '/widget.js';\n } catch {\n return WIDGET_SCRIPT_URL;\n }\n }\n\n return WIDGET_SCRIPT_URL;\n}\n\n/**\n * Validate website ID format\n */\nexport function validateWebsiteId(websiteId: string): boolean {\n // Basic validation: should be a non-empty string\n // Format varies, so we just check it's a reasonable string\n return typeof websiteId === 'string' && websiteId.length > 0 && websiteId.length < 100;\n}\n\n/**\n * Filter config to only include widget config (exclude websiteId)\n */\nexport function getConfigProps(config: BubblaVWidgetConfig): Omit<BubblaVWidgetConfig, 'websiteId'> {\n const { websiteId: _websiteId, ...configProps } = config;\n return configProps;\n}\n\n/**\n * Check if we're in a browser environment\n */\nexport function isBrowser(): boolean {\n return typeof window !== 'undefined' && typeof document !== 'undefined';\n}\n\n/**\n * Check if a widget script is already loaded\n */\nexport function isScriptLoaded(url: string): boolean {\n if (!isBrowser()) return false;\n\n const scripts = document.getElementsByTagName('script');\n for (let i = 0; i < scripts.length; i++) {\n if (scripts[i].src === url) {\n return true;\n }\n }\n return false;\n}\n","/**\n * BubblaVWidget Service\n *\n * Injectable service for programmatic access to the BubblaV SDK.\n *\n * @example\n * ```ts\n * constructor(private bubblav: BubblaVWidgetService) {\n * bubblav.open();\n * bubblav.sendMessage('Hello');\n * }\n * ```\n */\n\nimport { Injectable, NgZone, inject, DestroyRef } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { BehaviorSubject, Observable } from 'rxjs';\nimport { BubblaVWidgetSDK, BubblaVWidgetConfig, BubblaVSDK } from './types';\nimport {\n getWidgetScriptUrl,\n validateWebsiteId,\n propsToDataAttributes,\n getConfigProps,\n isBrowser,\n isScriptLoaded,\n} from './utils';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class BubblaVWidgetService implements BubblaVWidgetSDK {\n private ngZone = inject(NgZone);\n private destroyRef = inject(DestroyRef);\n\n private sdk: BubblaVSDK | null = null;\n private scriptElement: HTMLScriptElement | null = null;\n private isInitialized = false;\n\n // Track widget state\n private isOpenSubject = new BehaviorSubject<boolean>(false);\n public readonly isOpen$ = this.isOpenSubject.asObservable();\n\n constructor() {\n // Initialize SDK if already available\n if (isBrowser() && window.BubblaV) {\n this.sdk = window.BubblaV;\n this.setupEventListeners();\n }\n }\n\n /**\n * Initialize the widget with configuration\n */\n initialize(config: BubblaVWidgetConfig): void {\n if (!isBrowser()) {\n return;\n }\n\n if (this.isInitialized) {\n console.warn('[BubblaV] Widget already initialized');\n return;\n }\n\n // Validate website ID\n if (!validateWebsiteId(config.websiteId)) {\n console.warn(\n `[BubblaV] Invalid website ID format: \"${config.websiteId}\". ` +\n `Please check your website ID in the BubblaV dashboard.`\n );\n return;\n }\n\n // Get script URL\n const scriptUrl = getWidgetScriptUrl(config.apiUrl);\n\n // Check if script is already loaded\n if (isScriptLoaded(scriptUrl)) {\n console.warn(\n `[BubblaV] Widget script already loaded. ` +\n `Only one widget instance should be active.`\n );\n if (window.BubblaV) {\n this.sdk = window.BubblaV;\n this.setupEventListeners();\n }\n return;\n }\n\n // Mark as initialized\n this.isInitialized = true;\n\n // Create script element\n const script = document.createElement('script');\n script.src = scriptUrl;\n script.async = true;\n script.defer = true;\n\n // Set data attributes\n script.setAttribute('data-site-id', config.websiteId);\n\n // Set optional config as data attributes\n const configProps = getConfigProps(config);\n const dataAttrs = propsToDataAttributes(configProps);\n for (const [key, value] of Object.entries(dataAttrs)) {\n script.setAttribute(key, value);\n }\n\n // Handle load event\n script.onload = () => {\n this.ngZone.run(() => {\n if (window.BubblaV) {\n this.sdk = window.BubblaV;\n this.setupEventListeners();\n }\n });\n };\n\n // Handle error event\n script.onerror = () => {\n console.error(\n `[BubblaV] Failed to load widget script from ${scriptUrl}. ` +\n `Please check your network connection and ensure the URL is correct.`\n );\n this.isInitialized = false;\n };\n\n // Add script to document\n document.body.appendChild(script);\n this.scriptElement = script;\n }\n\n /**\n * Setup event listeners for widget state changes\n */\n private setupEventListeners(): void {\n if (!this.sdk) return;\n\n this.sdk.on('widget_opened', () => {\n this.ngZone.run(() => {\n this.isOpenSubject.next(true);\n });\n });\n\n this.sdk.on('widget_closed', () => {\n this.ngZone.run(() => {\n this.isOpenSubject.next(false);\n });\n });\n }\n\n /**\n * Cleanup widget\n */\n destroy(): void {\n if (this.scriptElement && this.scriptElement.parentNode) {\n this.scriptElement.parentNode.removeChild(this.scriptElement);\n }\n this.scriptElement = null;\n this.sdk = null;\n this.isInitialized = false;\n }\n\n // SDK Methods\n\n open(): void {\n this.sdk?.open();\n }\n\n close(): void {\n this.sdk?.close();\n }\n\n toggle(): void {\n this.sdk?.toggle();\n }\n\n isOpen(): boolean {\n return this.sdk?.isOpen() ?? false;\n }\n\n sendMessage(text: string, conversationId?: string): void {\n this.sdk?.sendMessage(text, conversationId);\n }\n\n showGreeting(message?: string): void {\n this.sdk?.showGreeting(message);\n }\n\n hideGreeting(): void {\n this.sdk?.hideGreeting();\n }\n\n getConfig(): Record<string, unknown> {\n return this.sdk?.getConfig() ?? {};\n }\n\n setDebug(enabled: boolean): void {\n this.sdk?.setDebug(enabled);\n }\n\n // Event methods\n\n /**\n * Listen to widget events\n */\n on(event: string, callback: (...args: unknown[]) => void): void {\n this.sdk?.on(event, callback);\n }\n\n /**\n * Unregister event listener\n */\n off(event: string, callback: (...args: unknown[]) => void): void {\n this.sdk?.off(event, callback);\n }\n\n /**\n * Emit event\n */\n emit(event: string, data?: unknown): void {\n this.sdk?.emit(event, data);\n }\n\n /**\n * Ready callback for widget loaded\n */\n ready(callback: () => void): void {\n this.sdk?.ready(callback);\n }\n\n /**\n * Track analytics event\n */\n track(eventName: string, properties?: Record<string, unknown>): void {\n this.sdk?.track(eventName, properties);\n }\n}\n","/**\n * BubblaVWidget Component\n *\n * Standalone Angular component for embedding the BubblaV chat widget.\n *\n * @example\n * ```html\n * <bubblav-widget\n * websiteId=\"your-website-id\"\n * bubbleColor=\"#3b82f6\"\n * desktopPosition=\"bottom-right\">\n * </bubblav-widget>\n * ```\n */\n\nimport {\n Component,\n Input,\n OnDestroy,\n OnInit,\n inject,\n ElementRef,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport { BubblaVWidgetService } from './bubblav-widget.service';\nimport { BubblaVWidgetConfig } from './types';\nimport { validateWebsiteId } from './utils';\n\n@Component({\n selector: 'bubblav-widget',\n template: '',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class BubblaVWidgetComponent implements OnInit, OnDestroy {\n private service = inject(BubblaVWidgetService);\n private elementRef = inject(ElementRef);\n\n // Required input\n @Input({ required: true }) websiteId!: string;\n\n // Optional styling inputs\n @Input() apiUrl?: string;\n @Input() bubbleColor?: string;\n @Input() bubbleIconColor?: string;\n @Input() desktopPosition?: 'bottom-left' | 'bottom-right';\n @Input() mobilePosition?: 'bottom-left' | 'bottom-right';\n @Input() poweredByVisible?: boolean;\n @Input() botName?: string;\n @Input() greetingMessage?: string;\n @Input() textboxPlaceholder?: string;\n @Input() showActionButtons?: boolean;\n\n private config: BubblaVWidgetConfig = {\n websiteId: '',\n };\n\n ngOnInit(): void {\n // Validate website ID\n if (!validateWebsiteId(this.websiteId)) {\n console.warn(\n `[BubblaV] Invalid website ID format: \"${this.websiteId}\". ` +\n `Please check your website ID in the BubblaV dashboard.`\n );\n return;\n }\n\n // Build config object\n this.config = {\n websiteId: this.websiteId,\n ...(this.apiUrl !== undefined && { apiUrl: this.apiUrl }),\n ...(this.bubbleColor !== undefined && { bubbleColor: this.bubbleColor }),\n ...(this.bubbleIconColor !== undefined && { bubbleIconColor: this.bubbleIconColor }),\n ...(this.desktopPosition !== undefined && { desktopPosition: this.desktopPosition }),\n ...(this.mobilePosition !== undefined && { mobilePosition: this.mobilePosition }),\n ...(this.poweredByVisible !== undefined && { poweredByVisible: this.poweredByVisible }),\n ...(this.botName !== undefined && { botName: this.botName }),\n ...(this.greetingMessage !== undefined && { greetingMessage: this.greetingMessage }),\n ...(this.textboxPlaceholder !== undefined && { textboxPlaceholder: this.textboxPlaceholder }),\n ...(this.showActionButtons !== undefined && { showActionButtons: this.showActionButtons }),\n };\n\n // Initialize widget\n this.service.initialize(this.config);\n }\n\n ngOnDestroy(): void {\n // Cleanup widget\n this.service.destroy();\n }\n}\n","/**\n * @bubblav/ai-chatbot-angular\n *\n * Angular component and service for embedding the BubblaV AI chatbot widget.\n *\n * @example\n * ```ts\n * import { BubblaVWidgetComponent } from '@bubblav/ai-chatbot-angular';\n * ```\n */\n\n// Component export\nexport { BubblaVWidgetComponent } from './lib/bubblav-widget.component';\n\n// Service export\nexport { BubblaVWidgetService } from './lib/bubblav-widget.service';\n\n// Type exports\nexport type {\n BubblaVWidgetConfig,\n BubblaVWidgetSDK,\n BubblaVSDK,\n} from './lib/types';\n\n// Utility exports\nexport {\n propsToDataAttributes,\n getWidgetScriptUrl,\n validateWebsiteId,\n getConfigProps,\n isBrowser,\n isScriptLoaded,\n} from './lib/utils';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,201 @@
1
+ import * as i0 from '@angular/core';
2
+ import { OnInit, OnDestroy } from '@angular/core';
3
+ import { Observable } from 'rxjs';
4
+
5
+ /**
6
+ * BubblaVWidget Component
7
+ *
8
+ * Standalone Angular component for embedding the BubblaV chat widget.
9
+ *
10
+ * @example
11
+ * ```html
12
+ * <bubblav-widget
13
+ * websiteId="your-website-id"
14
+ * bubbleColor="#3b82f6"
15
+ * desktopPosition="bottom-right">
16
+ * </bubblav-widget>
17
+ * ```
18
+ */
19
+
20
+ declare class BubblaVWidgetComponent implements OnInit, OnDestroy {
21
+ private service;
22
+ private elementRef;
23
+ websiteId: string;
24
+ apiUrl?: string;
25
+ bubbleColor?: string;
26
+ bubbleIconColor?: string;
27
+ desktopPosition?: 'bottom-left' | 'bottom-right';
28
+ mobilePosition?: 'bottom-left' | 'bottom-right';
29
+ poweredByVisible?: boolean;
30
+ botName?: string;
31
+ greetingMessage?: string;
32
+ textboxPlaceholder?: string;
33
+ showActionButtons?: boolean;
34
+ private config;
35
+ ngOnInit(): void;
36
+ ngOnDestroy(): void;
37
+ static ɵfac: i0.ɵɵFactoryDeclaration<BubblaVWidgetComponent, never>;
38
+ static ɵcmp: i0.ɵɵComponentDeclaration<BubblaVWidgetComponent, "bubblav-widget", never, { "websiteId": { "alias": "websiteId"; "required": true; }; "apiUrl": { "alias": "apiUrl"; "required": false; }; "bubbleColor": { "alias": "bubbleColor"; "required": false; }; "bubbleIconColor": { "alias": "bubbleIconColor"; "required": false; }; "desktopPosition": { "alias": "desktopPosition"; "required": false; }; "mobilePosition": { "alias": "mobilePosition"; "required": false; }; "poweredByVisible": { "alias": "poweredByVisible"; "required": false; }; "botName": { "alias": "botName"; "required": false; }; "greetingMessage": { "alias": "greetingMessage"; "required": false; }; "textboxPlaceholder": { "alias": "textboxPlaceholder"; "required": false; }; "showActionButtons": { "alias": "showActionButtons"; "required": false; }; }, {}, never, never, true, never>;
39
+ }
40
+
41
+ /**
42
+ * Type definitions for @bubblav/ai-chatbot-angular
43
+ */
44
+ /** Widget position options */
45
+ type WidgetPosition = 'bottom-left' | 'bottom-right';
46
+ /**
47
+ * Configuration for the BubblaV widget
48
+ */
49
+ interface BubblaVWidgetConfig {
50
+ /** Required: Your website ID from the BubblaV dashboard */
51
+ websiteId: string;
52
+ /** Optional: Custom API URL (defaults to production) */
53
+ apiUrl?: string;
54
+ /** Optional: Bubble button color (hex) */
55
+ bubbleColor?: string;
56
+ /** Optional: Bubble icon color (hex) */
57
+ bubbleIconColor?: string;
58
+ /** Optional: Desktop position */
59
+ desktopPosition?: WidgetPosition;
60
+ /** Optional: Mobile position */
61
+ mobilePosition?: WidgetPosition;
62
+ /** Optional: Show/hide powered by branding */
63
+ poweredByVisible?: boolean;
64
+ /** Optional: Custom bot name */
65
+ botName?: string;
66
+ /** Optional: Greeting message shown when widget opens */
67
+ greetingMessage?: string;
68
+ /** Optional: Placeholder text for input field */
69
+ textboxPlaceholder?: string;
70
+ /** Optional: Whether to show action buttons */
71
+ showActionButtons?: boolean;
72
+ }
73
+ /**
74
+ * SDK methods available via the service
75
+ */
76
+ interface BubblaVWidgetSDK {
77
+ /** Open the widget */
78
+ open(): void;
79
+ /** Close the widget */
80
+ close(): void;
81
+ /** Toggle widget open/close state */
82
+ toggle(): void;
83
+ /** Check if widget is currently open */
84
+ isOpen(): boolean;
85
+ /** Send a message programmatically */
86
+ sendMessage(text: string, conversationId?: string): void;
87
+ /** Show greeting message */
88
+ showGreeting(message?: string): void;
89
+ /** Hide greeting message */
90
+ hideGreeting(): void;
91
+ /** Get current widget configuration */
92
+ getConfig(): Record<string, unknown>;
93
+ /** Enable or disable debug mode */
94
+ setDebug(enabled: boolean): void;
95
+ }
96
+ /**
97
+ * Global window.BubblaV SDK interface
98
+ */
99
+ interface BubblaVSDK extends BubblaVWidgetSDK {
100
+ /** Register event listener */
101
+ on(event: string, callback: (...args: unknown[]) => void): void;
102
+ /** Unregister event listener */
103
+ off(event: string, callback: (...args: unknown[]) => void): void;
104
+ /** Emit event */
105
+ emit(event: string, data?: unknown): void;
106
+ /** Ready callback for widget loaded */
107
+ ready(callback: () => void): void;
108
+ /** Track analytics event */
109
+ track(eventName: string, properties?: Record<string, unknown>): void;
110
+ }
111
+ declare global {
112
+ interface Window {
113
+ BubblaV?: BubblaVSDK;
114
+ }
115
+ }
116
+
117
+ declare class BubblaVWidgetService implements BubblaVWidgetSDK {
118
+ private ngZone;
119
+ private destroyRef;
120
+ private sdk;
121
+ private scriptElement;
122
+ private isInitialized;
123
+ private isOpenSubject;
124
+ readonly isOpen$: Observable<boolean>;
125
+ constructor();
126
+ /**
127
+ * Initialize the widget with configuration
128
+ */
129
+ initialize(config: BubblaVWidgetConfig): void;
130
+ /**
131
+ * Setup event listeners for widget state changes
132
+ */
133
+ private setupEventListeners;
134
+ /**
135
+ * Cleanup widget
136
+ */
137
+ destroy(): void;
138
+ open(): void;
139
+ close(): void;
140
+ toggle(): void;
141
+ isOpen(): boolean;
142
+ sendMessage(text: string, conversationId?: string): void;
143
+ showGreeting(message?: string): void;
144
+ hideGreeting(): void;
145
+ getConfig(): Record<string, unknown>;
146
+ setDebug(enabled: boolean): void;
147
+ /**
148
+ * Listen to widget events
149
+ */
150
+ on(event: string, callback: (...args: unknown[]) => void): void;
151
+ /**
152
+ * Unregister event listener
153
+ */
154
+ off(event: string, callback: (...args: unknown[]) => void): void;
155
+ /**
156
+ * Emit event
157
+ */
158
+ emit(event: string, data?: unknown): void;
159
+ /**
160
+ * Ready callback for widget loaded
161
+ */
162
+ ready(callback: () => void): void;
163
+ /**
164
+ * Track analytics event
165
+ */
166
+ track(eventName: string, properties?: Record<string, unknown>): void;
167
+ static ɵfac: i0.ɵɵFactoryDeclaration<BubblaVWidgetService, never>;
168
+ static ɵprov: i0.ɵɵInjectableDeclaration<BubblaVWidgetService>;
169
+ }
170
+
171
+ /**
172
+ * Utility functions for @bubblav/ai-chatbot-angular
173
+ */
174
+
175
+ /**
176
+ * Convert camelCase props to data-* attribute names
177
+ */
178
+ declare function propsToDataAttributes(props: Record<string, unknown>): Record<string, string>;
179
+ /**
180
+ * Get the widget script URL
181
+ */
182
+ declare function getWidgetScriptUrl(apiUrl?: string): string;
183
+ /**
184
+ * Validate website ID format
185
+ */
186
+ declare function validateWebsiteId(websiteId: string): boolean;
187
+ /**
188
+ * Filter config to only include widget config (exclude websiteId)
189
+ */
190
+ declare function getConfigProps(config: BubblaVWidgetConfig): Omit<BubblaVWidgetConfig, 'websiteId'>;
191
+ /**
192
+ * Check if we're in a browser environment
193
+ */
194
+ declare function isBrowser(): boolean;
195
+ /**
196
+ * Check if a widget script is already loaded
197
+ */
198
+ declare function isScriptLoaded(url: string): boolean;
199
+
200
+ export { BubblaVWidgetComponent, BubblaVWidgetService, getConfigProps, getWidgetScriptUrl, isBrowser, isScriptLoaded, propsToDataAttributes, validateWebsiteId };
201
+ export type { BubblaVSDK, BubblaVWidgetConfig, BubblaVWidgetSDK };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@bubblav/ai-chatbot-angular",
3
+ "version": "1.0.0",
4
+ "description": "Angular component for embedding the BubblaV AI chatbot widget",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "ng-packagr -p ng-package.json",
21
+ "build:watch": "ng-packagr -p ng-package.json --watch",
22
+ "dev": "ng-packagr -p ng-package.json --watch",
23
+ "prepublishOnly": "npm run build",
24
+ "type-check": "tsc --noEmit"
25
+ },
26
+ "keywords": [
27
+ "bubblav",
28
+ "chatbot",
29
+ "ai",
30
+ "widget",
31
+ "angular",
32
+ "angular-component"
33
+ ],
34
+ "author": "BubblaV",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/tonnguyen/botcanchat.git",
39
+ "directory": "packages/ai-chatbot-angular"
40
+ },
41
+ "peerDependencies": {
42
+ "@angular/common": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
43
+ "@angular/core": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
44
+ "rxjs": "^6.5.0 || ^7.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "@angular/common": "^21.1.5",
48
+ "@angular/compiler": "^21.1.5",
49
+ "@angular/compiler-cli": "^21.1.5",
50
+ "@angular/core": "^21.1.5",
51
+ "ng-packagr": "^21.1.0",
52
+ "rxjs": "^7.8.1",
53
+ "typescript": "~5.9.3"
54
+ },
55
+ "engines": {
56
+ "node": ">=18.0.0"
57
+ },
58
+ "sideEffects": false
59
+ }