@bbq-chat/widgets-angular 1.0.3 → 1.0.4

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/README.md ADDED
@@ -0,0 +1,252 @@
1
+ # @bbq-chat/widgets-angular
2
+
3
+ Angular components and services for BbQ ChatWidgets. This package provides Angular-native wrappers for the core [@bbq-chat/widgets](https://www.npmjs.com/package/@bbq-chat/widgets) library.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @bbq-chat/widgets-angular @bbq-chat/widgets
9
+ ```
10
+
11
+ ## Peer Dependencies
12
+
13
+ This package requires:
14
+ - `@angular/common` >= 17.0.0
15
+ - `@angular/core` >= 17.0.0
16
+ - `@bbq-chat/widgets` ^1.0.0
17
+
18
+ ## Quick Start
19
+
20
+ ### 1. Import the component
21
+
22
+ ```typescript
23
+ import { Component } from '@angular/core';
24
+ import { WidgetRendererComponent } from '@bbq-chat/widgets-angular';
25
+ import type { ChatWidget } from '@bbq-chat/widgets-angular';
26
+
27
+ @Component({
28
+ selector: 'app-chat',
29
+ standalone: true,
30
+ imports: [WidgetRendererComponent],
31
+ template: `
32
+ <bbq-widget-renderer
33
+ [widgets]="widgets"
34
+ (widgetAction)="handleWidgetAction($event)">
35
+ </bbq-widget-renderer>
36
+ `
37
+ })
38
+ export class ChatComponent {
39
+ widgets: ChatWidget[] = [];
40
+
41
+ handleWidgetAction(event: { actionName: string; payload: any }) {
42
+ console.log('Widget action:', event.actionName, event.payload);
43
+ // Handle the action (e.g., send to backend)
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### 2. Import styles
49
+
50
+ In your `styles.css` or `styles.scss`:
51
+
52
+ ```css
53
+ @import '@bbq-chat/widgets/styles';
54
+ ```
55
+
56
+ Or import a specific theme:
57
+
58
+ ```css
59
+ @import '@bbq-chat/widgets/styles/light';
60
+ @import '@bbq-chat/widgets/styles/dark';
61
+ @import '@bbq-chat/widgets/styles/corporate';
62
+ ```
63
+
64
+ ## Components
65
+
66
+ ### WidgetRendererComponent
67
+
68
+ The main component for rendering chat widgets.
69
+
70
+ **Inputs:**
71
+ - `widgets: ChatWidget[] | null | undefined` - Array of widgets to render
72
+
73
+ **Outputs:**
74
+ - `widgetAction: EventEmitter<{ actionName: string; payload: any }>` - Emits when a widget action is triggered
75
+
76
+ **Example:**
77
+
78
+ ```typescript
79
+ <bbq-widget-renderer
80
+ [widgets]="messageWidgets"
81
+ (widgetAction)="onWidgetAction($event)">
82
+ </bbq-widget-renderer>
83
+ ```
84
+
85
+ ## Services
86
+
87
+ ### WidgetRegistryService
88
+
89
+ Service for registering custom widget types.
90
+
91
+ **Example:**
92
+
93
+ ```typescript
94
+ import { Component, OnInit } from '@angular/core';
95
+ import { WidgetRegistryService } from '@bbq-chat/widgets-angular';
96
+
97
+ export class MyCustomWidget {
98
+ type = 'myCustomWidget';
99
+ constructor(public label: string, public action: string) {}
100
+ }
101
+
102
+ @Component({
103
+ selector: 'app-root',
104
+ template: '...'
105
+ })
106
+ export class AppComponent implements OnInit {
107
+ constructor(private widgetRegistry: WidgetRegistryService) {}
108
+
109
+ ngOnInit() {
110
+ // Register custom widget factory
111
+ this.widgetRegistry.registerFactory('myCustomWidget', (obj) => {
112
+ if (obj.type === 'myCustomWidget') {
113
+ return new MyCustomWidget(obj.label, obj.action);
114
+ }
115
+ return null;
116
+ });
117
+ }
118
+ }
119
+ ```
120
+
121
+ ## Widget Types
122
+
123
+ All standard widget types from `@bbq-chat/widgets` are supported:
124
+
125
+ - `ButtonWidget` - Clickable buttons
126
+ - `CardWidget` - Information cards
127
+ - `FormWidget` - Form containers
128
+ - `InputWidget` - Text input fields
129
+ - `TextAreaWidget` - Multi-line text input
130
+ - `DropdownWidget` - Dropdown selects
131
+ - `SliderWidget` - Range sliders
132
+ - `ToggleWidget` - Toggle switches
133
+ - `FileUploadWidget` - File upload controls
134
+ - `DatePickerWidget` - Date pickers
135
+ - `MultiSelectWidget` - Multi-select dropdowns
136
+ - `ThemeSwitcherWidget` - Theme switcher controls
137
+ - `ProgressBarWidget` - Progress indicators
138
+ - `ImageWidget` - Image displays
139
+ - `ImageCollectionWidget` - Image galleries
140
+
141
+ ## Advanced Usage
142
+
143
+ ### Handling Widget Actions
144
+
145
+ Widget actions are emitted when users interact with widgets. Handle these actions in your component:
146
+
147
+ ```typescript
148
+ async handleWidgetAction(event: { actionName: string; payload: any }) {
149
+ const { actionName, payload } = event;
150
+
151
+ try {
152
+ const response = await fetch('/api/chat/action', {
153
+ method: 'POST',
154
+ headers: { 'Content-Type': 'application/json' },
155
+ body: JSON.stringify({ action: actionName, payload })
156
+ });
157
+
158
+ const data = await response.json();
159
+ // Update widgets with response
160
+ this.widgets = data.widgets;
161
+ } catch (error) {
162
+ console.error('Failed to handle widget action:', error);
163
+ }
164
+ }
165
+ ```
166
+
167
+ ### Custom Widget Renderers
168
+
169
+ The library now supports three types of custom widget renderers for enhanced flexibility:
170
+
171
+ #### 1. HTML Function Renderer
172
+
173
+ Simple function that returns HTML strings:
174
+
175
+ ```typescript
176
+ this.widgetRegistry.registerRenderer('myWidget', (widget) => {
177
+ return `<div class="my-widget">${widget.label}</div>`;
178
+ });
179
+ ```
180
+
181
+ #### 2. Angular Component Renderer (Recommended)
182
+
183
+ Use Angular components for full framework features including data binding, change detection, and dependency injection:
184
+
185
+ ```typescript
186
+ import { Component, Input } from '@angular/core';
187
+ import { CustomWidgetComponent } from '@bbq-chat/widgets-angular';
188
+
189
+ @Component({
190
+ selector: 'app-my-widget',
191
+ standalone: true,
192
+ template: `
193
+ <div class="my-widget">
194
+ <h3>{{ myWidget.title }}</h3>
195
+ <button (click)="onClick()">Action</button>
196
+ </div>
197
+ `
198
+ })
199
+ export class MyWidgetComponent implements CustomWidgetComponent {
200
+ @Input() widget!: ChatWidget;
201
+ widgetAction?: (actionName: string, payload: unknown) => void;
202
+
203
+ get myWidget(): MyCustomWidget {
204
+ return this.widget as MyCustomWidget;
205
+ }
206
+
207
+ onClick() {
208
+ this.widgetAction?.('my_action', { data: 'example' });
209
+ }
210
+ }
211
+
212
+ // Register the component
213
+ this.widgetRegistry.registerRenderer('myWidget', MyWidgetComponent);
214
+ ```
215
+
216
+ #### 3. Angular Template Renderer
217
+
218
+ Use inline templates with full Angular template syntax:
219
+
220
+ ```typescript
221
+ import { WidgetTemplateContext } from '@bbq-chat/widgets-angular';
222
+
223
+ @Component({
224
+ template: `
225
+ <ng-template #myTemplate let-widget let-emitAction="emitAction">
226
+ <div class="my-widget">
227
+ <h3>{{ widget.title }}</h3>
228
+ <button (click)="emitAction('my_action', { data: 'example' })">
229
+ Action
230
+ </button>
231
+ </div>
232
+ </ng-template>
233
+ `
234
+ })
235
+ export class AppComponent implements OnInit {
236
+ @ViewChild('myTemplate', { static: true }) myTemplate!: TemplateRef<WidgetTemplateContext>;
237
+
238
+ ngOnInit() {
239
+ this.widgetRegistry.registerRenderer('myWidget', this.myTemplate);
240
+ }
241
+ }
242
+ ```
243
+
244
+ See [EXAMPLES.md](./EXAMPLES.md) for detailed examples and best practices.
245
+
246
+ ## License
247
+
248
+ MIT
249
+
250
+ ## Repository
251
+
252
+ https://github.com/JeanMarcMbouma/BbQ.ChatWidgets
@@ -0,0 +1,542 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, Injectable, EventEmitter, createComponent, ViewChild, Output, Input, Inject, Component } from '@angular/core';
3
+ import { CommonModule } from '@angular/common';
4
+ import * as i2 from '@bbq-chat/widgets';
5
+ import { WidgetEventManager, SsrWidgetRenderer, customWidgetRegistry } from '@bbq-chat/widgets';
6
+ export { ChatWidget, SsrWidgetRenderer, WidgetEventManager, customWidgetRegistry } from '@bbq-chat/widgets';
7
+
8
+ /**
9
+ * Type guard to check if a renderer is a TemplateRef
10
+ */
11
+ function isTemplateRenderer(renderer) {
12
+ return (renderer !== null &&
13
+ typeof renderer === 'object' &&
14
+ 'createEmbeddedView' in renderer);
15
+ }
16
+ /**
17
+ * Type guard to check if a renderer is an Angular Component
18
+ *
19
+ * Note: This uses a heuristic check based on the following assumptions:
20
+ * 1. Components are constructor functions
21
+ * 2. Components have a prototype with a constructor property
22
+ * 3. Components typically use dependency injection (no required constructor params)
23
+ *
24
+ * Limitation: This may not detect components with required constructor parameters.
25
+ * For edge cases, explicitly check your component's constructor signature.
26
+ *
27
+ * Alternative: You can always register a wrapper component that has no required params.
28
+ */
29
+ function isComponentRenderer(renderer) {
30
+ // Check if it's a function (constructor) but not a regular function renderer
31
+ if (typeof renderer !== 'function') {
32
+ return false;
33
+ }
34
+ // Check for Angular component characteristics
35
+ // Components typically have prototype with constructor property
36
+ return (renderer.prototype !== undefined &&
37
+ renderer.prototype.constructor === renderer &&
38
+ renderer.length === 0 // Constructor with no required params (Angular DI)
39
+ );
40
+ }
41
+ /**
42
+ * Type guard to check if a renderer is an HTML function
43
+ *
44
+ * Note: This should be checked AFTER checking for component and template renderers
45
+ * since components are also functions but with additional properties.
46
+ */
47
+ function isHtmlRenderer(renderer) {
48
+ return typeof renderer === 'function';
49
+ }
50
+
51
+ const WIDGET_EVENT_MANAGER_FACTORY = new InjectionToken('WIDGET_EVENT_MANAGER_FACTORY');
52
+ /**
53
+ * Injection token for SsrWidgetRenderer
54
+ *
55
+ * Use this token to inject a SsrWidgetRenderer instance in your components.
56
+ * By default, WidgetRendererComponent provides this token with a factory that creates
57
+ * a new instance for each component.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * constructor(@Inject(SSR_WIDGET_RENDERER) private renderer: SsrWidgetRenderer) {}
62
+ * ```
63
+ */
64
+ const SSR_WIDGET_RENDERER = new InjectionToken('SSR_WIDGET_RENDERER');
65
+ /**
66
+ * Factory function for creating WidgetEventManager instances
67
+ *
68
+ * This factory is used by default in WidgetRendererComponent's providers array.
69
+ * You can override this in your own providers if you need custom initialization.
70
+ *
71
+ * @returns A factory function that creates WidgetEventManager instances
72
+ */
73
+ function widgetEventManagerFactoryProvider() {
74
+ return (actionHandler) => new WidgetEventManager(actionHandler);
75
+ }
76
+ /**
77
+ * Factory function for creating SsrWidgetRenderer instances
78
+ *
79
+ * This factory is used by default in WidgetRendererComponent's providers array.
80
+ * You can override this in your own providers if you need custom initialization
81
+ * or custom rendering options.
82
+ *
83
+ * @returns A new SsrWidgetRenderer instance
84
+ */
85
+ function ssrWidgetRendererFactory() {
86
+ return new SsrWidgetRenderer();
87
+ }
88
+
89
+ /**
90
+ * Service for registering custom widget factories and renderers
91
+ *
92
+ * This service provides a centralized way to register custom widget types
93
+ * that extend the base widget functionality, including support for
94
+ * Angular components and templates as custom renderers.
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * constructor(private widgetRegistry: WidgetRegistryService) {
99
+ * // Register a widget factory
100
+ * this.widgetRegistry.registerFactory('myWidget', (obj) => {
101
+ * if (obj.type === 'myWidget') {
102
+ * return new MyCustomWidget(obj.label, obj.action);
103
+ * }
104
+ * return null;
105
+ * });
106
+ *
107
+ * // Register a component-based renderer
108
+ * this.widgetRegistry.registerRenderer('myWidget', MyWidgetComponent);
109
+ * }
110
+ * ```
111
+ */
112
+ class WidgetRegistryService {
113
+ customRenderers = new Map();
114
+ /**
115
+ * Register a custom widget factory function
116
+ *
117
+ * @param type - The widget type identifier
118
+ * @param factory - Factory function that creates widget instances from plain objects
119
+ */
120
+ registerFactory(type, factory) {
121
+ customWidgetRegistry.registerFactory(type, factory);
122
+ }
123
+ /**
124
+ * Register a widget class with automatic factory creation
125
+ *
126
+ * @param type - The widget type identifier
127
+ * @param ctor - Widget class constructor
128
+ */
129
+ registerClass(type, ctor) {
130
+ customWidgetRegistry.registerClass(type, ctor);
131
+ }
132
+ /**
133
+ * Get a factory for a specific widget type
134
+ *
135
+ * @param type - The widget type identifier
136
+ * @returns The factory function if registered, undefined otherwise
137
+ */
138
+ getFactory(type) {
139
+ return customWidgetRegistry.getFactory(type);
140
+ }
141
+ /**
142
+ * Register a custom renderer for a specific widget type
143
+ *
144
+ * The renderer can be:
145
+ * - A function that returns HTML string
146
+ * - An Angular Component class
147
+ * - An Angular TemplateRef
148
+ *
149
+ * @param type - The widget type identifier
150
+ * @param renderer - The custom renderer (function, Component, or TemplateRef)
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * // HTML function renderer
155
+ * widgetRegistry.registerRenderer('weather', (widget) => `<div>${widget.label}</div>`);
156
+ *
157
+ * // Component renderer
158
+ * widgetRegistry.registerRenderer('weather', WeatherWidgetComponent);
159
+ *
160
+ * // Template renderer (from @ViewChild or elsewhere)
161
+ * widgetRegistry.registerRenderer('weather', this.weatherTemplate);
162
+ * ```
163
+ */
164
+ registerRenderer(type, renderer) {
165
+ if (!type || typeof type !== 'string') {
166
+ throw new Error('type must be a non-empty string');
167
+ }
168
+ if (!renderer) {
169
+ throw new Error('renderer is required');
170
+ }
171
+ this.customRenderers.set(type, renderer);
172
+ }
173
+ /**
174
+ * Get a custom renderer for a specific widget type
175
+ *
176
+ * @param type - The widget type identifier
177
+ * @returns The custom renderer if registered, undefined otherwise
178
+ */
179
+ getRenderer(type) {
180
+ return this.customRenderers.get(type);
181
+ }
182
+ /**
183
+ * Check if a custom renderer is registered for a widget type
184
+ *
185
+ * @param type - The widget type identifier
186
+ * @returns True if a custom renderer is registered, false otherwise
187
+ */
188
+ hasRenderer(type) {
189
+ return this.customRenderers.has(type);
190
+ }
191
+ /**
192
+ * Unregister a custom renderer for a widget type
193
+ *
194
+ * @param type - The widget type identifier
195
+ * @returns True if a renderer was removed, false if none was registered
196
+ */
197
+ unregisterRenderer(type) {
198
+ return this.customRenderers.delete(type);
199
+ }
200
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: WidgetRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
201
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: WidgetRegistryService, providedIn: 'root' });
202
+ }
203
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: WidgetRegistryService, decorators: [{
204
+ type: Injectable,
205
+ args: [{
206
+ providedIn: 'root',
207
+ }]
208
+ }] });
209
+
210
+ /**
211
+ * Angular component for rendering chat widgets
212
+ *
213
+ * This component handles rendering of chat widgets using the BbQ ChatWidgets library.
214
+ * It manages widget lifecycle, event handling, and cleanup.
215
+ *
216
+ * Supports three types of custom widget renderers:
217
+ * 1. HTML function renderers (return HTML strings)
218
+ * 2. Angular Component renderers (render as dynamic components)
219
+ * 3. Angular TemplateRef renderers (render as embedded views)
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * <bbq-widget-renderer
224
+ * [widgets]="messageWidgets"
225
+ * (widgetAction)="handleWidgetAction($event)">
226
+ * </bbq-widget-renderer>
227
+ * ```
228
+ */
229
+ class WidgetRendererComponent {
230
+ renderer;
231
+ eventManagerFactory;
232
+ widgetRegistry;
233
+ injector;
234
+ environmentInjector;
235
+ /**
236
+ * Array of widgets to render
237
+ */
238
+ widgets;
239
+ /**
240
+ * Emits when a widget action is triggered
241
+ */
242
+ widgetAction = new EventEmitter();
243
+ containerRef;
244
+ widgetItems = [];
245
+ eventManager;
246
+ isViewInitialized = false;
247
+ dynamicComponents = [];
248
+ dynamicViews = [];
249
+ constructor(renderer, eventManagerFactory, widgetRegistry, injector, environmentInjector) {
250
+ this.renderer = renderer;
251
+ this.eventManagerFactory = eventManagerFactory;
252
+ this.widgetRegistry = widgetRegistry;
253
+ this.injector = injector;
254
+ this.environmentInjector = environmentInjector;
255
+ }
256
+ ngOnInit() {
257
+ // this.updateWidgetHtml();
258
+ }
259
+ ngOnChanges(changes) {
260
+ if (changes['widgets']) {
261
+ this.updateWidgetHtml();
262
+ }
263
+ }
264
+ ngAfterViewInit() {
265
+ this.updateWidgetHtml();
266
+ this.isViewInitialized = true;
267
+ this.setupEventHandlers();
268
+ // Render dynamic components/templates after view init
269
+ this.renderDynamicWidgets();
270
+ }
271
+ ngOnDestroy() {
272
+ this.cleanup();
273
+ }
274
+ /**
275
+ * Base implementation for updating the rendered HTML for the current widgets.
276
+ *
277
+ * Subclasses may override this method to customize how widgets are rendered
278
+ * (for example, to inject additional markup or perform preprocessing).
279
+ *
280
+ * Since this is the base implementation, overriding implementations are not
281
+ * required to call `super.updateWidgetHtml()`.
282
+ */
283
+ updateWidgetHtml() {
284
+ if (!this.widgets || this.widgets.length === 0) {
285
+ this.widgetItems = [];
286
+ return;
287
+ }
288
+ this.widgetItems = this.widgets.map((widget, index) => {
289
+ const customRenderer = this.widgetRegistry.getRenderer(widget.type);
290
+ // Check template renderer first (most specific)
291
+ if (customRenderer && isTemplateRenderer(customRenderer)) {
292
+ return {
293
+ index,
294
+ widget,
295
+ isHtml: false,
296
+ };
297
+ }
298
+ // Check component renderer second
299
+ if (customRenderer && isComponentRenderer(customRenderer)) {
300
+ return {
301
+ index,
302
+ widget,
303
+ isHtml: false,
304
+ };
305
+ }
306
+ // Check HTML function renderer last (most general, matches any function)
307
+ if (customRenderer && isHtmlRenderer(customRenderer)) {
308
+ return {
309
+ index,
310
+ widget,
311
+ isHtml: true,
312
+ html: customRenderer(widget),
313
+ };
314
+ }
315
+ // Default: render using the BbQ library renderer
316
+ return {
317
+ index,
318
+ widget,
319
+ isHtml: true,
320
+ html: this.renderer.renderWidget(widget),
321
+ };
322
+ });
323
+ // After view updates, reinitialize widgets only if view is already initialized
324
+ if (this.isViewInitialized) {
325
+ setTimeout(() => {
326
+ this.setupEventHandlers();
327
+ this.renderDynamicWidgets();
328
+ }, 0);
329
+ }
330
+ }
331
+ /**
332
+ * Render dynamic components and templates for custom widgets
333
+ */
334
+ renderDynamicWidgets() {
335
+ if (!this.containerRef?.nativeElement)
336
+ return;
337
+ // Use microtask to ensure Angular has completed change detection
338
+ Promise.resolve().then(() => {
339
+ if (!this.containerRef?.nativeElement)
340
+ return;
341
+ // Clean up existing dynamic components and views
342
+ this.cleanupDynamicWidgets();
343
+ const container = this.containerRef.nativeElement;
344
+ // Query all widget divs without the data-rendered filter
345
+ const dynamicWidgetDivs = Array.from(container.querySelectorAll('.bbq-widget'));
346
+ let dynamicIndex = 0;
347
+ this.widgetItems.forEach((item) => {
348
+ if (!item.isHtml) {
349
+ const customRenderer = this.widgetRegistry.getRenderer(item.widget.type);
350
+ if (!customRenderer)
351
+ return;
352
+ const targetDiv = dynamicWidgetDivs[dynamicIndex];
353
+ if (!targetDiv)
354
+ return;
355
+ // Clear the div content before rendering
356
+ targetDiv.innerHTML = '';
357
+ if (isComponentRenderer(customRenderer)) {
358
+ this.renderComponent(customRenderer, item.widget, targetDiv);
359
+ }
360
+ else if (isTemplateRenderer(customRenderer)) {
361
+ this.renderTemplate(customRenderer, item.widget, targetDiv);
362
+ }
363
+ dynamicIndex++;
364
+ }
365
+ });
366
+ });
367
+ }
368
+ /**
369
+ * Render an Angular component for a custom widget
370
+ *
371
+ * Note: This method safely assigns properties to component instances
372
+ * by checking for property existence at runtime. This approach is necessary
373
+ * because we cannot statically verify that all components implement
374
+ * the CustomWidgetComponent interface.
375
+ */
376
+ renderComponent(componentType, widget, targetElement) {
377
+ // Create the component using Angular's createComponent API
378
+ const componentRef = createComponent(componentType, {
379
+ environmentInjector: this.environmentInjector,
380
+ elementInjector: this.injector,
381
+ });
382
+ // Safely set component inputs if they exist
383
+ const instance = componentRef.instance;
384
+ // Set widget property if it exists in the prototype chain
385
+ instance['widget'] = widget;
386
+ // Set widgetAction property if it exists in the prototype chain
387
+ instance['widgetAction'] = (actionName, payload) => {
388
+ this.widgetAction.emit({ actionName, payload });
389
+ };
390
+ // Attach the component's host view to the target element
391
+ targetElement.appendChild(componentRef.location.nativeElement);
392
+ // Store reference for cleanup
393
+ this.dynamicComponents.push(componentRef);
394
+ // Trigger change detection (use optional chaining for safety)
395
+ componentRef.changeDetectorRef?.detectChanges();
396
+ }
397
+ /**
398
+ * Render an Angular template for a custom widget
399
+ */
400
+ renderTemplate(templateRef, widget, targetElement) {
401
+ const context = {
402
+ $implicit: widget,
403
+ widget: widget,
404
+ emitAction: (actionName, payload) => {
405
+ this.widgetAction.emit({ actionName, payload });
406
+ },
407
+ };
408
+ const viewRef = templateRef.createEmbeddedView(context);
409
+ // Attach the view's DOM nodes to the target element
410
+ viewRef.rootNodes.forEach((node) => {
411
+ targetElement.appendChild(node);
412
+ });
413
+ // Store reference for cleanup
414
+ this.dynamicViews.push(viewRef);
415
+ // Trigger change detection
416
+ viewRef.detectChanges();
417
+ }
418
+ /**
419
+ * Cleanup dynamic components and views
420
+ */
421
+ cleanupDynamicWidgets() {
422
+ this.dynamicComponents.forEach((componentRef) => {
423
+ componentRef.destroy();
424
+ });
425
+ this.dynamicComponents = [];
426
+ this.dynamicViews.forEach((viewRef) => {
427
+ viewRef.destroy();
428
+ });
429
+ this.dynamicViews = [];
430
+ }
431
+ setupEventHandlers() {
432
+ if (!this.containerRef?.nativeElement)
433
+ return;
434
+ // Cleanup old resources before setting up new ones
435
+ this.cleanup();
436
+ const container = this.containerRef.nativeElement;
437
+ // Create a custom action handler that emits events
438
+ const actionHandler = {
439
+ handle: async (action, payload) => {
440
+ this.widgetAction.emit({ actionName: action, payload });
441
+ },
442
+ };
443
+ // Use the injected factory to create an event manager with the component-specific action handler
444
+ this.eventManager = this.eventManagerFactory(actionHandler);
445
+ this.eventManager.attachHandlers(container);
446
+ }
447
+ handleClick(event) {
448
+ const target = event.target;
449
+ // Only trigger actions on non-form buttons and clickable elements (cards)
450
+ // Don't trigger on input elements or form buttons (let WidgetEventManager handle those)
451
+ const button = target.tagName === 'BUTTON' ? target : target.closest('button');
452
+ if (button && !button.closest('[data-widget-type="form"]')) {
453
+ const actionName = button.getAttribute('data-action');
454
+ if (actionName) {
455
+ try {
456
+ const payloadStr = button.getAttribute('data-payload');
457
+ const payload = payloadStr ? JSON.parse(payloadStr) : {};
458
+ this.widgetAction.emit({ actionName, payload });
459
+ }
460
+ catch (err) {
461
+ console.error('Failed to parse widget action payload:', err);
462
+ }
463
+ }
464
+ }
465
+ }
466
+ /**
467
+ * Cleanup all resources including event listeners.
468
+ */
469
+ cleanup() {
470
+ // Cleanup dynamic widgets first
471
+ this.cleanupDynamicWidgets();
472
+ // Cleanup event manager
473
+ this.eventManager = undefined;
474
+ }
475
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: WidgetRendererComponent, deps: [{ token: SSR_WIDGET_RENDERER }, { token: WIDGET_EVENT_MANAGER_FACTORY }, { token: WidgetRegistryService }, { token: i0.Injector }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Component });
476
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: WidgetRendererComponent, isStandalone: true, selector: "bbq-widget-renderer", inputs: { widgets: "widgets" }, outputs: { widgetAction: "widgetAction" }, providers: [
477
+ { provide: WIDGET_EVENT_MANAGER_FACTORY, useFactory: widgetEventManagerFactoryProvider },
478
+ { provide: SSR_WIDGET_RENDERER, useFactory: ssrWidgetRendererFactory },
479
+ ], viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["widgetContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
480
+ <div #widgetContainer class="bbq-widgets-container" (click)="handleClick($event)">
481
+ @for (item of widgetItems; track item.index) {
482
+ @if (item.isHtml) {
483
+ <div class="bbq-widget" [innerHTML]="item.html"></div>
484
+ } @else {
485
+ <div class="bbq-widget" #dynamicWidget></div>
486
+ }
487
+ }
488
+ </div>
489
+ `, isInline: true, styles: [".bbq-widgets-container{margin-top:.5rem}.bbq-widget{margin-bottom:.5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
490
+ }
491
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: WidgetRendererComponent, decorators: [{
492
+ type: Component,
493
+ args: [{ selector: 'bbq-widget-renderer', standalone: true, imports: [CommonModule], providers: [
494
+ { provide: WIDGET_EVENT_MANAGER_FACTORY, useFactory: widgetEventManagerFactoryProvider },
495
+ { provide: SSR_WIDGET_RENDERER, useFactory: ssrWidgetRendererFactory },
496
+ ], template: `
497
+ <div #widgetContainer class="bbq-widgets-container" (click)="handleClick($event)">
498
+ @for (item of widgetItems; track item.index) {
499
+ @if (item.isHtml) {
500
+ <div class="bbq-widget" [innerHTML]="item.html"></div>
501
+ } @else {
502
+ <div class="bbq-widget" #dynamicWidget></div>
503
+ }
504
+ }
505
+ </div>
506
+ `, styles: [".bbq-widgets-container{margin-top:.5rem}.bbq-widget{margin-bottom:.5rem}\n"] }]
507
+ }], ctorParameters: () => [{ type: i2.SsrWidgetRenderer, decorators: [{
508
+ type: Inject,
509
+ args: [SSR_WIDGET_RENDERER]
510
+ }] }, { type: undefined, decorators: [{
511
+ type: Inject,
512
+ args: [WIDGET_EVENT_MANAGER_FACTORY]
513
+ }] }, { type: WidgetRegistryService }, { type: i0.Injector }, { type: i0.EnvironmentInjector }], propDecorators: { widgets: [{
514
+ type: Input
515
+ }], widgetAction: [{
516
+ type: Output
517
+ }], containerRef: [{
518
+ type: ViewChild,
519
+ args: ['widgetContainer', { static: false }]
520
+ }] } });
521
+
522
+ /**
523
+ * @bbq-chat/widgets-angular
524
+ *
525
+ * Angular components and services for BbQ ChatWidgets
526
+ *
527
+ * This package provides Angular-native components and services that wrap
528
+ * the core @bbq-chat/widgets library, making it easy to integrate chat
529
+ * widgets into Angular applications.
530
+ *
531
+ * @packageDocumentation
532
+ */
533
+ // Export components
534
+ // Version
535
+ const VERSION = '1.0.3';
536
+
537
+ /**
538
+ * Generated bundle index. Do not edit.
539
+ */
540
+
541
+ export { SSR_WIDGET_RENDERER, VERSION, WIDGET_EVENT_MANAGER_FACTORY, WidgetRegistryService, WidgetRendererComponent, isComponentRenderer, isHtmlRenderer, isTemplateRenderer, ssrWidgetRendererFactory, widgetEventManagerFactoryProvider };
542
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../../src/custom-widget-renderer.types.ts","../../src/widget-di.tokens.ts","../../src/widget-registry.service.ts","../../src/widget-renderer.component.ts","../../src/public_api.ts","../../src/index.ts"],"sourcesContent":["import { Type, TemplateRef } from '@angular/core';\r\nimport { ChatWidget } from '@bbq-chat/widgets';\r\n\r\n/**\r\n * Context provided to template-based custom widget renderers\r\n */\r\nexport interface WidgetTemplateContext {\r\n /**\r\n * The widget instance being rendered\r\n */\r\n $implicit: ChatWidget;\r\n \r\n /**\r\n * The widget instance (alternative access)\r\n */\r\n widget: ChatWidget;\r\n \r\n /**\r\n * Emit a widget action\r\n */\r\n emitAction: (actionName: string, payload: unknown) => void;\r\n}\r\n\r\n/**\r\n * Interface for component-based custom widget renderers\r\n */\r\nexport interface CustomWidgetComponent {\r\n /**\r\n * The widget instance to render\r\n */\r\n widget: ChatWidget;\r\n \r\n /**\r\n * Event emitter for widget actions (optional, will be set by the renderer)\r\n */\r\n widgetAction?: (actionName: string, payload: unknown) => void;\r\n}\r\n\r\n/**\r\n * Type for custom widget renderer functions that return HTML strings\r\n */\r\nexport type CustomWidgetHtmlRenderer = (widget: ChatWidget) => string;\r\n\r\n/**\r\n * Type for custom widget renderer configurations\r\n */\r\nexport type CustomWidgetRenderer =\r\n | CustomWidgetHtmlRenderer\r\n | Type<CustomWidgetComponent>\r\n | TemplateRef<WidgetTemplateContext>;\r\n\r\n/**\r\n * Configuration for registering a custom widget renderer\r\n */\r\nexport interface CustomWidgetRendererConfig {\r\n /**\r\n * The widget type identifier\r\n */\r\n type: string;\r\n \r\n /**\r\n * The renderer: can be a function returning HTML, an Angular Component class, or a TemplateRef\r\n */\r\n renderer: CustomWidgetRenderer;\r\n}\r\n\r\n/**\r\n * Type guard to check if a renderer is a TemplateRef\r\n */\r\nexport function isTemplateRenderer(\r\n renderer: CustomWidgetRenderer\r\n): renderer is TemplateRef<WidgetTemplateContext> {\r\n return (\r\n renderer !== null &&\r\n typeof renderer === 'object' &&\r\n 'createEmbeddedView' in renderer\r\n );\r\n}\r\n\r\n/**\r\n * Type guard to check if a renderer is an Angular Component\r\n * \r\n * Note: This uses a heuristic check based on the following assumptions:\r\n * 1. Components are constructor functions\r\n * 2. Components have a prototype with a constructor property\r\n * 3. Components typically use dependency injection (no required constructor params)\r\n * \r\n * Limitation: This may not detect components with required constructor parameters.\r\n * For edge cases, explicitly check your component's constructor signature.\r\n * \r\n * Alternative: You can always register a wrapper component that has no required params.\r\n */\r\nexport function isComponentRenderer(\r\n renderer: CustomWidgetRenderer\r\n): renderer is Type<CustomWidgetComponent> {\r\n // Check if it's a function (constructor) but not a regular function renderer\r\n if (typeof renderer !== 'function') {\r\n return false;\r\n }\r\n \r\n // Check for Angular component characteristics\r\n // Components typically have prototype with constructor property\r\n return (\r\n renderer.prototype !== undefined &&\r\n renderer.prototype.constructor === renderer &&\r\n renderer.length === 0 // Constructor with no required params (Angular DI)\r\n );\r\n}\r\n\r\n/**\r\n * Type guard to check if a renderer is an HTML function\r\n * \r\n * Note: This should be checked AFTER checking for component and template renderers\r\n * since components are also functions but with additional properties.\r\n */\r\nexport function isHtmlRenderer(\r\n renderer: CustomWidgetRenderer\r\n): renderer is CustomWidgetHtmlRenderer {\r\n return typeof renderer === 'function';\r\n}\r\n","import { InjectionToken } from '@angular/core';\r\nimport { SsrWidgetRenderer, WidgetEventManager, IWidgetActionHandler } from '@bbq-chat/widgets';\r\n\r\n/**\r\n * Injection token for WidgetEventManager factory\r\n * \r\n * Use this token to inject a factory function that creates WidgetEventManager instances.\r\n * The factory accepts an optional action handler to configure the manager.\r\n * \r\n * @example\r\n * ```typescript\r\n * constructor(@Inject(WIDGET_EVENT_MANAGER_FACTORY) private eventManagerFactory: WidgetEventManagerFactory) {\r\n * const actionHandler = { handle: async (action, payload) => { ... } };\r\n * this.eventManager = this.eventManagerFactory(actionHandler);\r\n * }\r\n * ```\r\n */\r\nexport type WidgetEventManagerFactory = (actionHandler?: IWidgetActionHandler) => WidgetEventManager;\r\n\r\nexport const WIDGET_EVENT_MANAGER_FACTORY = new InjectionToken<WidgetEventManagerFactory>(\r\n 'WIDGET_EVENT_MANAGER_FACTORY'\r\n);\r\n\r\n/**\r\n * Injection token for SsrWidgetRenderer\r\n * \r\n * Use this token to inject a SsrWidgetRenderer instance in your components.\r\n * By default, WidgetRendererComponent provides this token with a factory that creates\r\n * a new instance for each component.\r\n * \r\n * @example\r\n * ```typescript\r\n * constructor(@Inject(SSR_WIDGET_RENDERER) private renderer: SsrWidgetRenderer) {}\r\n * ```\r\n */\r\nexport const SSR_WIDGET_RENDERER = new InjectionToken<SsrWidgetRenderer>(\r\n 'SSR_WIDGET_RENDERER'\r\n);\r\n\r\n/**\r\n * Factory function for creating WidgetEventManager instances\r\n * \r\n * This factory is used by default in WidgetRendererComponent's providers array.\r\n * You can override this in your own providers if you need custom initialization.\r\n * \r\n * @returns A factory function that creates WidgetEventManager instances\r\n */\r\nexport function widgetEventManagerFactoryProvider(): WidgetEventManagerFactory {\r\n return (actionHandler?: IWidgetActionHandler) => new WidgetEventManager(actionHandler);\r\n}\r\n\r\n/**\r\n * Factory function for creating SsrWidgetRenderer instances\r\n * \r\n * This factory is used by default in WidgetRendererComponent's providers array.\r\n * You can override this in your own providers if you need custom initialization\r\n * or custom rendering options.\r\n * \r\n * @returns A new SsrWidgetRenderer instance\r\n */\r\nexport function ssrWidgetRendererFactory(): SsrWidgetRenderer {\r\n return new SsrWidgetRenderer();\r\n}\r\n","import { Injectable } from '@angular/core';\r\nimport { customWidgetRegistry, ChatWidget } from '@bbq-chat/widgets';\r\nimport { CustomWidgetRenderer } from './custom-widget-renderer.types';\r\n\r\n/**\r\n * Service for registering custom widget factories and renderers\r\n * \r\n * This service provides a centralized way to register custom widget types\r\n * that extend the base widget functionality, including support for\r\n * Angular components and templates as custom renderers.\r\n * \r\n * @example\r\n * ```typescript\r\n * constructor(private widgetRegistry: WidgetRegistryService) {\r\n * // Register a widget factory\r\n * this.widgetRegistry.registerFactory('myWidget', (obj) => {\r\n * if (obj.type === 'myWidget') {\r\n * return new MyCustomWidget(obj.label, obj.action);\r\n * }\r\n * return null;\r\n * });\r\n * \r\n * // Register a component-based renderer\r\n * this.widgetRegistry.registerRenderer('myWidget', MyWidgetComponent);\r\n * }\r\n * ```\r\n */\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class WidgetRegistryService {\r\n private readonly customRenderers = new Map<string, CustomWidgetRenderer>();\r\n /**\r\n * Register a custom widget factory function\r\n * \r\n * @param type - The widget type identifier\r\n * @param factory - Factory function that creates widget instances from plain objects\r\n */\r\n registerFactory(\r\n type: string,\r\n factory: (obj: unknown) => ChatWidget | null\r\n ): void {\r\n customWidgetRegistry.registerFactory(type, factory);\r\n }\r\n\r\n /**\r\n * Register a widget class with automatic factory creation\r\n * \r\n * @param type - The widget type identifier\r\n * @param ctor - Widget class constructor\r\n */\r\n registerClass(type: string, ctor: any): void {\r\n customWidgetRegistry.registerClass(type, ctor);\r\n }\r\n\r\n /**\r\n * Get a factory for a specific widget type\r\n * \r\n * @param type - The widget type identifier\r\n * @returns The factory function if registered, undefined otherwise\r\n */\r\n getFactory(type: string): ((obj: any) => ChatWidget | null) | undefined {\r\n return customWidgetRegistry.getFactory(type);\r\n }\r\n\r\n /**\r\n * Register a custom renderer for a specific widget type\r\n * \r\n * The renderer can be:\r\n * - A function that returns HTML string\r\n * - An Angular Component class\r\n * - An Angular TemplateRef\r\n * \r\n * @param type - The widget type identifier\r\n * @param renderer - The custom renderer (function, Component, or TemplateRef)\r\n * \r\n * @example\r\n * ```typescript\r\n * // HTML function renderer\r\n * widgetRegistry.registerRenderer('weather', (widget) => `<div>${widget.label}</div>`);\r\n * \r\n * // Component renderer\r\n * widgetRegistry.registerRenderer('weather', WeatherWidgetComponent);\r\n * \r\n * // Template renderer (from @ViewChild or elsewhere)\r\n * widgetRegistry.registerRenderer('weather', this.weatherTemplate);\r\n * ```\r\n */\r\n registerRenderer(type: string, renderer: CustomWidgetRenderer): void {\r\n if (!type || typeof type !== 'string') {\r\n throw new Error('type must be a non-empty string');\r\n }\r\n if (!renderer) {\r\n throw new Error('renderer is required');\r\n }\r\n this.customRenderers.set(type, renderer);\r\n }\r\n\r\n /**\r\n * Get a custom renderer for a specific widget type\r\n * \r\n * @param type - The widget type identifier\r\n * @returns The custom renderer if registered, undefined otherwise\r\n */\r\n getRenderer(type: string): CustomWidgetRenderer | undefined {\r\n return this.customRenderers.get(type);\r\n }\r\n\r\n /**\r\n * Check if a custom renderer is registered for a widget type\r\n * \r\n * @param type - The widget type identifier\r\n * @returns True if a custom renderer is registered, false otherwise\r\n */\r\n hasRenderer(type: string): boolean {\r\n return this.customRenderers.has(type);\r\n }\r\n\r\n /**\r\n * Unregister a custom renderer for a widget type\r\n * \r\n * @param type - The widget type identifier\r\n * @returns True if a renderer was removed, false if none was registered\r\n */\r\n unregisterRenderer(type: string): boolean {\r\n return this.customRenderers.delete(type);\r\n }\r\n}\r\n","import {\r\n Component,\r\n Input,\r\n Output,\r\n EventEmitter,\r\n ElementRef,\r\n AfterViewInit,\r\n OnInit,\r\n OnDestroy,\r\n OnChanges,\r\n SimpleChanges,\r\n ViewChild,\r\n ComponentRef,\r\n EmbeddedViewRef,\r\n TemplateRef,\r\n Injector,\r\n createComponent,\r\n EnvironmentInjector,\r\n Inject,\r\n} from '@angular/core';\r\nimport { CommonModule } from '@angular/common';\r\nimport {\r\n SsrWidgetRenderer,\r\n WidgetEventManager,\r\n ChatWidget,\r\n} from '@bbq-chat/widgets';\r\nimport { WidgetRegistryService } from './widget-registry.service';\r\nimport {\r\n WidgetTemplateContext,\r\n isHtmlRenderer,\r\n isComponentRenderer,\r\n isTemplateRenderer,\r\n} from './custom-widget-renderer.types';\r\nimport {\r\n WIDGET_EVENT_MANAGER_FACTORY,\r\n SSR_WIDGET_RENDERER,\r\n widgetEventManagerFactoryProvider,\r\n ssrWidgetRendererFactory,\r\n WidgetEventManagerFactory,\r\n} from './widget-di.tokens';\r\n\r\n/**\r\n * Angular component for rendering chat widgets\r\n * \r\n * This component handles rendering of chat widgets using the BbQ ChatWidgets library.\r\n * It manages widget lifecycle, event handling, and cleanup. \r\n * \r\n * Supports three types of custom widget renderers:\r\n * 1. HTML function renderers (return HTML strings)\r\n * 2. Angular Component renderers (render as dynamic components)\r\n * 3. Angular TemplateRef renderers (render as embedded views)\r\n * \r\n * @example\r\n * ```typescript\r\n * <bbq-widget-renderer \r\n * [widgets]=\"messageWidgets\" \r\n * (widgetAction)=\"handleWidgetAction($event)\">\r\n * </bbq-widget-renderer>\r\n * ```\r\n */\r\n@Component({\r\n selector: 'bbq-widget-renderer',\r\n standalone: true,\r\n imports: [CommonModule],\r\n providers: [\r\n { provide: WIDGET_EVENT_MANAGER_FACTORY, useFactory: widgetEventManagerFactoryProvider },\r\n { provide: SSR_WIDGET_RENDERER, useFactory: ssrWidgetRendererFactory },\r\n ],\r\n template: `\r\n <div #widgetContainer class=\"bbq-widgets-container\" (click)=\"handleClick($event)\">\r\n @for (item of widgetItems; track item.index) {\r\n @if (item.isHtml) {\r\n <div class=\"bbq-widget\" [innerHTML]=\"item.html\"></div>\r\n } @else {\r\n <div class=\"bbq-widget\" #dynamicWidget></div>\r\n }\r\n }\r\n </div>\r\n `,\r\n styles: [\r\n `\r\n .bbq-widgets-container {\r\n margin-top: 0.5rem;\r\n }\r\n\r\n .bbq-widget {\r\n margin-bottom: 0.5rem;\r\n }\r\n `,\r\n ],\r\n})\r\nexport class WidgetRendererComponent\r\n implements OnInit, AfterViewInit, OnDestroy, OnChanges {\r\n /**\r\n * Array of widgets to render\r\n */\r\n @Input() widgets: ChatWidget[] | null | undefined;\r\n\r\n /**\r\n * Emits when a widget action is triggered\r\n */\r\n @Output() widgetAction = new EventEmitter<{\r\n actionName: string;\r\n payload: unknown;\r\n }>();\r\n\r\n @ViewChild('widgetContainer', { static: false })\r\n containerRef!: ElementRef<HTMLDivElement>;\r\n\r\n protected widgetItems: Array<{\r\n index: number;\r\n widget: ChatWidget;\r\n isHtml: boolean;\r\n html?: string;\r\n }> = [];\r\n\r\n protected eventManager?: WidgetEventManager;\r\n protected isViewInitialized = false;\r\n protected dynamicComponents: Array<ComponentRef<any>> = [];\r\n protected dynamicViews: Array<EmbeddedViewRef<WidgetTemplateContext>> = [];\r\n\r\n constructor(\r\n @Inject(SSR_WIDGET_RENDERER) protected renderer: SsrWidgetRenderer,\r\n @Inject(WIDGET_EVENT_MANAGER_FACTORY) protected eventManagerFactory: WidgetEventManagerFactory,\r\n protected widgetRegistry: WidgetRegistryService,\r\n protected injector: Injector,\r\n protected environmentInjector: EnvironmentInjector\r\n ) { }\r\n\r\n ngOnInit() {\r\n // this.updateWidgetHtml();\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges) {\r\n if (changes['widgets']) {\r\n this.updateWidgetHtml();\r\n }\r\n }\r\n\r\n ngAfterViewInit() {\r\n this.updateWidgetHtml();\r\n this.isViewInitialized = true;\r\n this.setupEventHandlers();\r\n // Render dynamic components/templates after view init\r\n this.renderDynamicWidgets();\r\n }\r\n\r\n ngOnDestroy() {\r\n this.cleanup();\r\n }\r\n\r\n /**\r\n * Base implementation for updating the rendered HTML for the current widgets.\r\n *\r\n * Subclasses may override this method to customize how widgets are rendered\r\n * (for example, to inject additional markup or perform preprocessing).\r\n *\r\n * Since this is the base implementation, overriding implementations are not\r\n * required to call `super.updateWidgetHtml()`.\r\n */\r\n protected updateWidgetHtml() {\r\n if (!this.widgets || this.widgets.length === 0) {\r\n this.widgetItems = [];\r\n return;\r\n }\r\n\r\n this.widgetItems = this.widgets.map((widget, index) => {\r\n const customRenderer = this.widgetRegistry.getRenderer(widget.type);\r\n\r\n // Check template renderer first (most specific)\r\n if (customRenderer && isTemplateRenderer(customRenderer)) {\r\n return {\r\n index,\r\n widget,\r\n isHtml: false,\r\n };\r\n }\r\n\r\n // Check component renderer second\r\n if (customRenderer && isComponentRenderer(customRenderer)) {\r\n return {\r\n index,\r\n widget,\r\n isHtml: false,\r\n };\r\n }\r\n\r\n // Check HTML function renderer last (most general, matches any function)\r\n if (customRenderer && isHtmlRenderer(customRenderer)) {\r\n return {\r\n index,\r\n widget,\r\n isHtml: true,\r\n html: customRenderer(widget),\r\n };\r\n }\r\n\r\n // Default: render using the BbQ library renderer\r\n return {\r\n index,\r\n widget,\r\n isHtml: true,\r\n html: this.renderer.renderWidget(widget),\r\n };\r\n });\r\n\r\n // After view updates, reinitialize widgets only if view is already initialized\r\n if (this.isViewInitialized) {\r\n setTimeout(() => {\r\n this.setupEventHandlers();\r\n this.renderDynamicWidgets();\r\n }, 0);\r\n }\r\n }\r\n\r\n /**\r\n * Render dynamic components and templates for custom widgets\r\n */\r\n protected renderDynamicWidgets() {\r\n if (!this.containerRef?.nativeElement) return;\r\n\r\n // Use microtask to ensure Angular has completed change detection\r\n Promise.resolve().then(() => {\r\n if (!this.containerRef?.nativeElement) return;\r\n\r\n // Clean up existing dynamic components and views\r\n this.cleanupDynamicWidgets();\r\n\r\n const container = this.containerRef.nativeElement;\r\n // Query all widget divs without the data-rendered filter\r\n const dynamicWidgetDivs = Array.from(\r\n container.querySelectorAll('.bbq-widget')\r\n ) as HTMLElement[];\r\n\r\n let dynamicIndex = 0;\r\n this.widgetItems.forEach((item) => {\r\n if (!item.isHtml) {\r\n const customRenderer = this.widgetRegistry.getRenderer(item.widget.type);\r\n\r\n if (!customRenderer) return;\r\n\r\n const targetDiv = dynamicWidgetDivs[dynamicIndex];\r\n if (!targetDiv) return;\r\n\r\n // Clear the div content before rendering\r\n targetDiv.innerHTML = '';\r\n\r\n if (isComponentRenderer(customRenderer)) {\r\n this.renderComponent(customRenderer, item.widget, targetDiv);\r\n } else if (isTemplateRenderer(customRenderer)) {\r\n this.renderTemplate(customRenderer, item.widget, targetDiv);\r\n }\r\n\r\n dynamicIndex++;\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Render an Angular component for a custom widget\r\n * \r\n * Note: This method safely assigns properties to component instances\r\n * by checking for property existence at runtime. This approach is necessary\r\n * because we cannot statically verify that all components implement\r\n * the CustomWidgetComponent interface.\r\n */\r\n protected renderComponent(\r\n componentType: any,\r\n widget: ChatWidget,\r\n targetElement: HTMLElement\r\n ) {\r\n // Create the component using Angular's createComponent API\r\n const componentRef = createComponent(componentType, {\r\n environmentInjector: this.environmentInjector,\r\n elementInjector: this.injector,\r\n });\r\n\r\n // Safely set component inputs if they exist\r\n const instance = componentRef.instance as any;\r\n // Set widget property if it exists in the prototype chain\r\n instance['widget'] = widget;\r\n // Set widgetAction property if it exists in the prototype chain\r\n instance['widgetAction'] = (actionName: string, payload: unknown) => {\r\n this.widgetAction.emit({ actionName, payload });\r\n };\r\n // Attach the component's host view to the target element\r\n targetElement.appendChild(componentRef.location.nativeElement);\r\n\r\n // Store reference for cleanup\r\n this.dynamicComponents.push(componentRef);\r\n\r\n // Trigger change detection (use optional chaining for safety)\r\n componentRef.changeDetectorRef?.detectChanges();\r\n }\r\n\r\n /**\r\n * Render an Angular template for a custom widget\r\n */\r\n protected renderTemplate(\r\n templateRef: TemplateRef<WidgetTemplateContext>,\r\n widget: ChatWidget,\r\n targetElement: HTMLElement\r\n ) {\r\n const context: WidgetTemplateContext = {\r\n $implicit: widget,\r\n widget: widget,\r\n emitAction: (actionName: string, payload: unknown) => {\r\n this.widgetAction.emit({ actionName, payload });\r\n },\r\n };\r\n\r\n const viewRef = templateRef.createEmbeddedView(context);\r\n\r\n // Attach the view's DOM nodes to the target element\r\n viewRef.rootNodes.forEach((node: Node) => {\r\n targetElement.appendChild(node);\r\n });\r\n\r\n // Store reference for cleanup\r\n this.dynamicViews.push(viewRef);\r\n\r\n // Trigger change detection\r\n viewRef.detectChanges();\r\n }\r\n\r\n /**\r\n * Cleanup dynamic components and views\r\n */\r\n protected cleanupDynamicWidgets() {\r\n this.dynamicComponents.forEach((componentRef) => {\r\n componentRef.destroy();\r\n });\r\n this.dynamicComponents = [];\r\n\r\n this.dynamicViews.forEach((viewRef) => {\r\n viewRef.destroy();\r\n });\r\n this.dynamicViews = [];\r\n }\r\n\r\n private setupEventHandlers() {\r\n if (!this.containerRef?.nativeElement) return;\r\n\r\n // Cleanup old resources before setting up new ones\r\n this.cleanup();\r\n\r\n const container = this.containerRef.nativeElement;\r\n\r\n // Create a custom action handler that emits events\r\n const actionHandler = {\r\n handle: async (action: string, payload: any) => {\r\n this.widgetAction.emit({ actionName: action, payload });\r\n },\r\n };\r\n\r\n // Use the injected factory to create an event manager with the component-specific action handler\r\n this.eventManager = this.eventManagerFactory(actionHandler);\r\n this.eventManager.attachHandlers(container);\r\n }\r\n\r\n handleClick(event: MouseEvent) {\r\n const target = event.target as HTMLElement;\r\n // Only trigger actions on non-form buttons and clickable elements (cards)\r\n // Don't trigger on input elements or form buttons (let WidgetEventManager handle those)\r\n const button = target.tagName === 'BUTTON' ? target : target.closest('button');\r\n if (button && !button.closest('[data-widget-type=\"form\"]')) {\r\n const actionName = button.getAttribute('data-action');\r\n if (actionName) {\r\n try {\r\n const payloadStr = button.getAttribute('data-payload');\r\n const payload = payloadStr ? JSON.parse(payloadStr) : {};\r\n this.widgetAction.emit({ actionName, payload });\r\n } catch (err) {\r\n console.error('Failed to parse widget action payload:', err);\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Cleanup all resources including event listeners.\r\n */\r\n private cleanup() {\r\n // Cleanup dynamic widgets first\r\n this.cleanupDynamicWidgets();\r\n\r\n // Cleanup event manager\r\n this.eventManager = undefined;\r\n }\r\n}\r\n","/**\r\n * @bbq-chat/widgets-angular\r\n * \r\n * Angular components and services for BbQ ChatWidgets\r\n * \r\n * This package provides Angular-native components and services that wrap\r\n * the core @bbq-chat/widgets library, making it easy to integrate chat\r\n * widgets into Angular applications.\r\n * \r\n * @packageDocumentation\r\n */\r\n\r\n// Export components\r\nexport { WidgetRendererComponent } from './widget-renderer.component';\r\n\r\n// Export services\r\nexport { WidgetRegistryService } from './widget-registry.service';\r\n\r\n// Export DI tokens and factories\r\nexport {\r\n WIDGET_EVENT_MANAGER_FACTORY,\r\n SSR_WIDGET_RENDERER,\r\n widgetEventManagerFactoryProvider,\r\n ssrWidgetRendererFactory,\r\n} from './widget-di.tokens';\r\n\r\nexport type { WidgetEventManagerFactory } from './widget-di.tokens';\r\n\r\n// Export custom widget renderer types\r\nexport type {\r\n CustomWidgetComponent,\r\n CustomWidgetRenderer,\r\n CustomWidgetHtmlRenderer,\r\n CustomWidgetRendererConfig,\r\n WidgetTemplateContext,\r\n} from './custom-widget-renderer.types';\r\n\r\nexport {\r\n isHtmlRenderer,\r\n isComponentRenderer,\r\n isTemplateRenderer,\r\n} from './custom-widget-renderer.types';\r\n\r\n// Re-export commonly used types and classes from core package\r\nexport {\r\n ChatWidget,\r\n} from '@bbq-chat/widgets';\r\n\r\nexport type {\r\n ButtonWidget,\r\n CardWidget,\r\n FormWidget,\r\n InputWidget,\r\n TextAreaWidget,\r\n DropdownWidget,\r\n SliderWidget,\r\n ToggleWidget,\r\n FileUploadWidget,\r\n DatePickerWidget,\r\n MultiSelectWidget,\r\n ProgressBarWidget,\r\n ThemeSwitcherWidget,\r\n ImageWidget,\r\n ImageCollectionWidget,\r\n} from '@bbq-chat/widgets';\r\n\r\n// Re-export utilities\r\nexport {\r\n SsrWidgetRenderer,\r\n WidgetEventManager,\r\n customWidgetRegistry,\r\n} from '@bbq-chat/widgets';\r\n\r\n// Version\r\nexport const VERSION = '1.0.3';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public_api';\n"],"names":["i1.WidgetRegistryService"],"mappings":"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"}
@@ -0,0 +1,335 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Type, TemplateRef, InjectionToken, OnInit, AfterViewInit, OnDestroy, OnChanges, Injector, EnvironmentInjector, EventEmitter, ElementRef, ComponentRef, EmbeddedViewRef, SimpleChanges } from '@angular/core';
3
+ import { ChatWidget, IWidgetActionHandler, WidgetEventManager, SsrWidgetRenderer } from '@bbq-chat/widgets';
4
+ export { ButtonWidget, CardWidget, ChatWidget, DatePickerWidget, DropdownWidget, FileUploadWidget, FormWidget, ImageCollectionWidget, ImageWidget, InputWidget, MultiSelectWidget, ProgressBarWidget, SliderWidget, SsrWidgetRenderer, TextAreaWidget, ThemeSwitcherWidget, ToggleWidget, WidgetEventManager, customWidgetRegistry } from '@bbq-chat/widgets';
5
+
6
+ /**
7
+ * Context provided to template-based custom widget renderers
8
+ */
9
+ interface WidgetTemplateContext {
10
+ /**
11
+ * The widget instance being rendered
12
+ */
13
+ $implicit: ChatWidget;
14
+ /**
15
+ * The widget instance (alternative access)
16
+ */
17
+ widget: ChatWidget;
18
+ /**
19
+ * Emit a widget action
20
+ */
21
+ emitAction: (actionName: string, payload: unknown) => void;
22
+ }
23
+ /**
24
+ * Interface for component-based custom widget renderers
25
+ */
26
+ interface CustomWidgetComponent {
27
+ /**
28
+ * The widget instance to render
29
+ */
30
+ widget: ChatWidget;
31
+ /**
32
+ * Event emitter for widget actions (optional, will be set by the renderer)
33
+ */
34
+ widgetAction?: (actionName: string, payload: unknown) => void;
35
+ }
36
+ /**
37
+ * Type for custom widget renderer functions that return HTML strings
38
+ */
39
+ type CustomWidgetHtmlRenderer = (widget: ChatWidget) => string;
40
+ /**
41
+ * Type for custom widget renderer configurations
42
+ */
43
+ type CustomWidgetRenderer = CustomWidgetHtmlRenderer | Type<CustomWidgetComponent> | TemplateRef<WidgetTemplateContext>;
44
+ /**
45
+ * Configuration for registering a custom widget renderer
46
+ */
47
+ interface CustomWidgetRendererConfig {
48
+ /**
49
+ * The widget type identifier
50
+ */
51
+ type: string;
52
+ /**
53
+ * The renderer: can be a function returning HTML, an Angular Component class, or a TemplateRef
54
+ */
55
+ renderer: CustomWidgetRenderer;
56
+ }
57
+ /**
58
+ * Type guard to check if a renderer is a TemplateRef
59
+ */
60
+ declare function isTemplateRenderer(renderer: CustomWidgetRenderer): renderer is TemplateRef<WidgetTemplateContext>;
61
+ /**
62
+ * Type guard to check if a renderer is an Angular Component
63
+ *
64
+ * Note: This uses a heuristic check based on the following assumptions:
65
+ * 1. Components are constructor functions
66
+ * 2. Components have a prototype with a constructor property
67
+ * 3. Components typically use dependency injection (no required constructor params)
68
+ *
69
+ * Limitation: This may not detect components with required constructor parameters.
70
+ * For edge cases, explicitly check your component's constructor signature.
71
+ *
72
+ * Alternative: You can always register a wrapper component that has no required params.
73
+ */
74
+ declare function isComponentRenderer(renderer: CustomWidgetRenderer): renderer is Type<CustomWidgetComponent>;
75
+ /**
76
+ * Type guard to check if a renderer is an HTML function
77
+ *
78
+ * Note: This should be checked AFTER checking for component and template renderers
79
+ * since components are also functions but with additional properties.
80
+ */
81
+ declare function isHtmlRenderer(renderer: CustomWidgetRenderer): renderer is CustomWidgetHtmlRenderer;
82
+
83
+ /**
84
+ * Service for registering custom widget factories and renderers
85
+ *
86
+ * This service provides a centralized way to register custom widget types
87
+ * that extend the base widget functionality, including support for
88
+ * Angular components and templates as custom renderers.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * constructor(private widgetRegistry: WidgetRegistryService) {
93
+ * // Register a widget factory
94
+ * this.widgetRegistry.registerFactory('myWidget', (obj) => {
95
+ * if (obj.type === 'myWidget') {
96
+ * return new MyCustomWidget(obj.label, obj.action);
97
+ * }
98
+ * return null;
99
+ * });
100
+ *
101
+ * // Register a component-based renderer
102
+ * this.widgetRegistry.registerRenderer('myWidget', MyWidgetComponent);
103
+ * }
104
+ * ```
105
+ */
106
+ declare class WidgetRegistryService {
107
+ private readonly customRenderers;
108
+ /**
109
+ * Register a custom widget factory function
110
+ *
111
+ * @param type - The widget type identifier
112
+ * @param factory - Factory function that creates widget instances from plain objects
113
+ */
114
+ registerFactory(type: string, factory: (obj: unknown) => ChatWidget | null): void;
115
+ /**
116
+ * Register a widget class with automatic factory creation
117
+ *
118
+ * @param type - The widget type identifier
119
+ * @param ctor - Widget class constructor
120
+ */
121
+ registerClass(type: string, ctor: any): void;
122
+ /**
123
+ * Get a factory for a specific widget type
124
+ *
125
+ * @param type - The widget type identifier
126
+ * @returns The factory function if registered, undefined otherwise
127
+ */
128
+ getFactory(type: string): ((obj: any) => ChatWidget | null) | undefined;
129
+ /**
130
+ * Register a custom renderer for a specific widget type
131
+ *
132
+ * The renderer can be:
133
+ * - A function that returns HTML string
134
+ * - An Angular Component class
135
+ * - An Angular TemplateRef
136
+ *
137
+ * @param type - The widget type identifier
138
+ * @param renderer - The custom renderer (function, Component, or TemplateRef)
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * // HTML function renderer
143
+ * widgetRegistry.registerRenderer('weather', (widget) => `<div>${widget.label}</div>`);
144
+ *
145
+ * // Component renderer
146
+ * widgetRegistry.registerRenderer('weather', WeatherWidgetComponent);
147
+ *
148
+ * // Template renderer (from @ViewChild or elsewhere)
149
+ * widgetRegistry.registerRenderer('weather', this.weatherTemplate);
150
+ * ```
151
+ */
152
+ registerRenderer(type: string, renderer: CustomWidgetRenderer): void;
153
+ /**
154
+ * Get a custom renderer for a specific widget type
155
+ *
156
+ * @param type - The widget type identifier
157
+ * @returns The custom renderer if registered, undefined otherwise
158
+ */
159
+ getRenderer(type: string): CustomWidgetRenderer | undefined;
160
+ /**
161
+ * Check if a custom renderer is registered for a widget type
162
+ *
163
+ * @param type - The widget type identifier
164
+ * @returns True if a custom renderer is registered, false otherwise
165
+ */
166
+ hasRenderer(type: string): boolean;
167
+ /**
168
+ * Unregister a custom renderer for a widget type
169
+ *
170
+ * @param type - The widget type identifier
171
+ * @returns True if a renderer was removed, false if none was registered
172
+ */
173
+ unregisterRenderer(type: string): boolean;
174
+ static ɵfac: i0.ɵɵFactoryDeclaration<WidgetRegistryService, never>;
175
+ static ɵprov: i0.ɵɵInjectableDeclaration<WidgetRegistryService>;
176
+ }
177
+
178
+ /**
179
+ * Injection token for WidgetEventManager factory
180
+ *
181
+ * Use this token to inject a factory function that creates WidgetEventManager instances.
182
+ * The factory accepts an optional action handler to configure the manager.
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * constructor(@Inject(WIDGET_EVENT_MANAGER_FACTORY) private eventManagerFactory: WidgetEventManagerFactory) {
187
+ * const actionHandler = { handle: async (action, payload) => { ... } };
188
+ * this.eventManager = this.eventManagerFactory(actionHandler);
189
+ * }
190
+ * ```
191
+ */
192
+ type WidgetEventManagerFactory = (actionHandler?: IWidgetActionHandler) => WidgetEventManager;
193
+ declare const WIDGET_EVENT_MANAGER_FACTORY: InjectionToken<WidgetEventManagerFactory>;
194
+ /**
195
+ * Injection token for SsrWidgetRenderer
196
+ *
197
+ * Use this token to inject a SsrWidgetRenderer instance in your components.
198
+ * By default, WidgetRendererComponent provides this token with a factory that creates
199
+ * a new instance for each component.
200
+ *
201
+ * @example
202
+ * ```typescript
203
+ * constructor(@Inject(SSR_WIDGET_RENDERER) private renderer: SsrWidgetRenderer) {}
204
+ * ```
205
+ */
206
+ declare const SSR_WIDGET_RENDERER: InjectionToken<SsrWidgetRenderer>;
207
+ /**
208
+ * Factory function for creating WidgetEventManager instances
209
+ *
210
+ * This factory is used by default in WidgetRendererComponent's providers array.
211
+ * You can override this in your own providers if you need custom initialization.
212
+ *
213
+ * @returns A factory function that creates WidgetEventManager instances
214
+ */
215
+ declare function widgetEventManagerFactoryProvider(): WidgetEventManagerFactory;
216
+ /**
217
+ * Factory function for creating SsrWidgetRenderer instances
218
+ *
219
+ * This factory is used by default in WidgetRendererComponent's providers array.
220
+ * You can override this in your own providers if you need custom initialization
221
+ * or custom rendering options.
222
+ *
223
+ * @returns A new SsrWidgetRenderer instance
224
+ */
225
+ declare function ssrWidgetRendererFactory(): SsrWidgetRenderer;
226
+
227
+ /**
228
+ * Angular component for rendering chat widgets
229
+ *
230
+ * This component handles rendering of chat widgets using the BbQ ChatWidgets library.
231
+ * It manages widget lifecycle, event handling, and cleanup.
232
+ *
233
+ * Supports three types of custom widget renderers:
234
+ * 1. HTML function renderers (return HTML strings)
235
+ * 2. Angular Component renderers (render as dynamic components)
236
+ * 3. Angular TemplateRef renderers (render as embedded views)
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * <bbq-widget-renderer
241
+ * [widgets]="messageWidgets"
242
+ * (widgetAction)="handleWidgetAction($event)">
243
+ * </bbq-widget-renderer>
244
+ * ```
245
+ */
246
+ declare class WidgetRendererComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges {
247
+ protected renderer: SsrWidgetRenderer;
248
+ protected eventManagerFactory: WidgetEventManagerFactory;
249
+ protected widgetRegistry: WidgetRegistryService;
250
+ protected injector: Injector;
251
+ protected environmentInjector: EnvironmentInjector;
252
+ /**
253
+ * Array of widgets to render
254
+ */
255
+ widgets: ChatWidget[] | null | undefined;
256
+ /**
257
+ * Emits when a widget action is triggered
258
+ */
259
+ widgetAction: EventEmitter<{
260
+ actionName: string;
261
+ payload: unknown;
262
+ }>;
263
+ containerRef: ElementRef<HTMLDivElement>;
264
+ protected widgetItems: Array<{
265
+ index: number;
266
+ widget: ChatWidget;
267
+ isHtml: boolean;
268
+ html?: string;
269
+ }>;
270
+ protected eventManager?: WidgetEventManager;
271
+ protected isViewInitialized: boolean;
272
+ protected dynamicComponents: Array<ComponentRef<any>>;
273
+ protected dynamicViews: Array<EmbeddedViewRef<WidgetTemplateContext>>;
274
+ constructor(renderer: SsrWidgetRenderer, eventManagerFactory: WidgetEventManagerFactory, widgetRegistry: WidgetRegistryService, injector: Injector, environmentInjector: EnvironmentInjector);
275
+ ngOnInit(): void;
276
+ ngOnChanges(changes: SimpleChanges): void;
277
+ ngAfterViewInit(): void;
278
+ ngOnDestroy(): void;
279
+ /**
280
+ * Base implementation for updating the rendered HTML for the current widgets.
281
+ *
282
+ * Subclasses may override this method to customize how widgets are rendered
283
+ * (for example, to inject additional markup or perform preprocessing).
284
+ *
285
+ * Since this is the base implementation, overriding implementations are not
286
+ * required to call `super.updateWidgetHtml()`.
287
+ */
288
+ protected updateWidgetHtml(): void;
289
+ /**
290
+ * Render dynamic components and templates for custom widgets
291
+ */
292
+ protected renderDynamicWidgets(): void;
293
+ /**
294
+ * Render an Angular component for a custom widget
295
+ *
296
+ * Note: This method safely assigns properties to component instances
297
+ * by checking for property existence at runtime. This approach is necessary
298
+ * because we cannot statically verify that all components implement
299
+ * the CustomWidgetComponent interface.
300
+ */
301
+ protected renderComponent(componentType: any, widget: ChatWidget, targetElement: HTMLElement): void;
302
+ /**
303
+ * Render an Angular template for a custom widget
304
+ */
305
+ protected renderTemplate(templateRef: TemplateRef<WidgetTemplateContext>, widget: ChatWidget, targetElement: HTMLElement): void;
306
+ /**
307
+ * Cleanup dynamic components and views
308
+ */
309
+ protected cleanupDynamicWidgets(): void;
310
+ private setupEventHandlers;
311
+ handleClick(event: MouseEvent): void;
312
+ /**
313
+ * Cleanup all resources including event listeners.
314
+ */
315
+ private cleanup;
316
+ static ɵfac: i0.ɵɵFactoryDeclaration<WidgetRendererComponent, never>;
317
+ static ɵcmp: i0.ɵɵComponentDeclaration<WidgetRendererComponent, "bbq-widget-renderer", never, { "widgets": { "alias": "widgets"; "required": false; }; }, { "widgetAction": "widgetAction"; }, never, never, true, never>;
318
+ }
319
+
320
+ /**
321
+ * @bbq-chat/widgets-angular
322
+ *
323
+ * Angular components and services for BbQ ChatWidgets
324
+ *
325
+ * This package provides Angular-native components and services that wrap
326
+ * the core @bbq-chat/widgets library, making it easy to integrate chat
327
+ * widgets into Angular applications.
328
+ *
329
+ * @packageDocumentation
330
+ */
331
+
332
+ declare const VERSION = "1.0.3";
333
+
334
+ export { SSR_WIDGET_RENDERER, VERSION, WIDGET_EVENT_MANAGER_FACTORY, WidgetRegistryService, WidgetRendererComponent, isComponentRenderer, isHtmlRenderer, isTemplateRenderer, ssrWidgetRendererFactory, widgetEventManagerFactoryProvider };
335
+ export type { CustomWidgetComponent, CustomWidgetHtmlRenderer, CustomWidgetRenderer, CustomWidgetRendererConfig, WidgetEventManagerFactory, WidgetTemplateContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbq-chat/widgets-angular",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Angular components and services for BbQ ChatWidgets",
5
5
  "license": "MIT",
6
6
  "author": "BbQ ChatWidgets Contributors",
@@ -27,6 +27,13 @@
27
27
  "README.md",
28
28
  "LICENSE"
29
29
  ],
30
+ "scripts": {
31
+ "ng": "ng",
32
+ "start": "ng serve",
33
+ "build": "ng build",
34
+ "watch": "ng build --watch --configuration development",
35
+ "test": "ng test"
36
+ },
30
37
  "packageManager": "npm@10.9.0",
31
38
  "peerDependencies": {
32
39
  "@angular/common": "^21.0.0",
@@ -38,18 +45,14 @@
38
45
  "rxjs": "~7.8.0",
39
46
  "@bbq-chat/widgets": "^1.0.0"
40
47
  },
41
- "module": "fesm2022/index.mjs",
42
- "typings": "types/index.d.ts",
43
- "exports": {
44
- "./package.json": {
45
- "default": "./package.json"
46
- },
47
- ".": {
48
- "types": "./types/index.d.ts",
49
- "default": "./fesm2022/index.mjs"
50
- }
51
- },
52
- "dependencies": {
53
- "tslib": "^2.3.0"
48
+ "devDependencies": {
49
+ "@angular/build": "^21.0.5",
50
+ "@angular/cli": "^21.0.5",
51
+ "@angular/compiler-cli": "^21.0.0",
52
+ "@bbq-chat/widgets": "file:../js",
53
+ "jsdom": "^27.1.0",
54
+ "ng-packagr": "^21.0.0",
55
+ "typescript": "~5.9.2",
56
+ "vitest": "^4.0.8"
54
57
  }
55
- }
58
+ }