@bbq-chat/widgets-angular 1.0.8 → 1.0.9
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/.angular/cache/21.0.5/ng-packagr/97cbacd0e5e4cb18d1fead4d7f3aee1c3863ba3ffbe7cb7dd7780f237a848a5c +1 -0
- package/.angular/cache/21.0.5/ng-packagr/tsbuildinfo/index.tsbuildinfo +1 -0
- package/.eslintrc.json +23 -0
- package/.prettierrc.json +8 -0
- package/EXAMPLES.md +484 -0
- package/README.md +119 -0
- package/angular.json +36 -0
- package/ng-package.json +9 -0
- package/package.json +20 -15
- package/src/angular-widget-renderer.spec.ts +157 -0
- package/src/components/button.component.ts +35 -0
- package/src/components/card.component.ts +52 -0
- package/src/components/datepicker.component.ts +63 -0
- package/src/components/dropdown.component.ts +65 -0
- package/src/components/fileupload.component.ts +71 -0
- package/src/components/form.component.ts +433 -0
- package/src/components/image.component.ts +33 -0
- package/src/components/imagecollection.component.ts +39 -0
- package/src/components/index.ts +20 -0
- package/src/components/input.component.ts +63 -0
- package/src/components/multiselect.component.ts +67 -0
- package/src/components/progressbar.component.ts +50 -0
- package/src/components/slider.component.ts +67 -0
- package/src/components/textarea.component.ts +63 -0
- package/src/components/themeswitcher.component.ts +46 -0
- package/src/components/toggle.component.ts +63 -0
- package/src/custom-widget-renderer.types.ts +120 -0
- package/src/examples/form-validation-listener.component.ts +41 -0
- package/src/public_api.ts +107 -0
- package/src/renderers/AngularWidgetRenderer.ts +100 -0
- package/src/renderers/built-in-components.ts +41 -0
- package/src/renderers/index.ts +7 -0
- package/src/services/form-validation.service.ts +21 -0
- package/src/widget-di.tokens.ts +95 -0
- package/src/widget-registry.service.ts +128 -0
- package/src/widget-renderer.component.ts +421 -0
- package/tsconfig.json +37 -0
- package/tsconfig.lib.json +18 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +13 -0
- package/fesm2022/index.mjs +0 -1992
- package/fesm2022/index.mjs.map +0 -1
- package/types/index.d.ts +0 -618
package/EXAMPLES.md
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# Usage Examples
|
|
2
|
+
|
|
3
|
+
## Understanding Widget Rendering
|
|
4
|
+
|
|
5
|
+
The `@bbq-chat/widgets-angular` package uses the **AngularWidgetRenderer** to render all built-in widgets as native Angular components. This provides better performance, type safety, and full Angular framework integration compared to HTML string rendering.
|
|
6
|
+
|
|
7
|
+
### Automatic Angular Rendering
|
|
8
|
+
|
|
9
|
+
When you use `WidgetRendererComponent`, it automatically uses `AngularWidgetRenderer` for all built-in widget types:
|
|
10
|
+
|
|
11
|
+
- ✅ **Built-in widgets** (button, card, form, etc.) → Rendered as Angular components
|
|
12
|
+
- ✅ **Custom component renderers** → Your Angular components
|
|
13
|
+
- ✅ **Custom template renderers** → Your Angular templates
|
|
14
|
+
- ✅ **HTML function renderers** → Your custom HTML strings
|
|
15
|
+
- ✅ **Fallback** → SSR renderer for compatibility
|
|
16
|
+
|
|
17
|
+
No configuration needed - it just works!
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
### 1. Install the package
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @bbq-chat/widgets-angular @bbq-chat/widgets
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Import styles in your global styles file
|
|
28
|
+
|
|
29
|
+
In `styles.css` or `styles.scss`:
|
|
30
|
+
|
|
31
|
+
```css
|
|
32
|
+
@import '@bbq-chat/widgets/styles';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 3. Create a chat component
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { Component } from '@angular/core';
|
|
39
|
+
import { CommonModule } from '@angular/common';
|
|
40
|
+
import { WidgetRendererComponent } from '@bbq-chat/widgets-angular';
|
|
41
|
+
import type { ChatWidget } from '@bbq-chat/widgets-angular';
|
|
42
|
+
|
|
43
|
+
@Component({
|
|
44
|
+
selector: 'app-chat',
|
|
45
|
+
standalone: true,
|
|
46
|
+
imports: [CommonModule, WidgetRendererComponent],
|
|
47
|
+
template: `
|
|
48
|
+
<div class="chat-container">
|
|
49
|
+
<div class="messages">
|
|
50
|
+
@for (message of messages; track message.id) {
|
|
51
|
+
<div class="message">
|
|
52
|
+
<div class="message-content">{{ message.content }}</div>
|
|
53
|
+
<bbq-widget-renderer
|
|
54
|
+
[widgets]="message.widgets"
|
|
55
|
+
(widgetAction)="handleWidgetAction($event)">
|
|
56
|
+
</bbq-widget-renderer>
|
|
57
|
+
</div>
|
|
58
|
+
}
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="input-area">
|
|
62
|
+
<input
|
|
63
|
+
type="text"
|
|
64
|
+
[(ngModel)]="userInput"
|
|
65
|
+
(keyup.enter)="sendMessage()"
|
|
66
|
+
placeholder="Type a message..."
|
|
67
|
+
/>
|
|
68
|
+
<button (click)="sendMessage()">Send</button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
`,
|
|
72
|
+
styles: [`
|
|
73
|
+
.chat-container {
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
height: 100vh;
|
|
77
|
+
max-width: 800px;
|
|
78
|
+
margin: 0 auto;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.messages {
|
|
82
|
+
flex: 1;
|
|
83
|
+
overflow-y: auto;
|
|
84
|
+
padding: 1rem;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.message {
|
|
88
|
+
margin-bottom: 1rem;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.message-content {
|
|
92
|
+
margin-bottom: 0.5rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.input-area {
|
|
96
|
+
display: flex;
|
|
97
|
+
gap: 0.5rem;
|
|
98
|
+
padding: 1rem;
|
|
99
|
+
border-top: 1px solid #ccc;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
input {
|
|
103
|
+
flex: 1;
|
|
104
|
+
padding: 0.5rem;
|
|
105
|
+
}
|
|
106
|
+
`]
|
|
107
|
+
})
|
|
108
|
+
export class ChatComponent {
|
|
109
|
+
messages: Array<{ id: number; content: string; widgets?: ChatWidget[] }> = [];
|
|
110
|
+
userInput = '';
|
|
111
|
+
private messageId = 0;
|
|
112
|
+
|
|
113
|
+
async sendMessage() {
|
|
114
|
+
if (!this.userInput.trim()) return;
|
|
115
|
+
|
|
116
|
+
// Add user message
|
|
117
|
+
this.messages.push({
|
|
118
|
+
id: this.messageId++,
|
|
119
|
+
content: this.userInput,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const userMessage = this.userInput;
|
|
123
|
+
this.userInput = '';
|
|
124
|
+
|
|
125
|
+
// Send to backend
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch('/api/chat/message', {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: { 'Content-Type': 'application/json' },
|
|
130
|
+
body: JSON.stringify({ message: userMessage }),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const data = await response.json();
|
|
134
|
+
|
|
135
|
+
// Add assistant response with widgets
|
|
136
|
+
this.messages.push({
|
|
137
|
+
id: this.messageId++,
|
|
138
|
+
content: data.content || '',
|
|
139
|
+
widgets: data.widgets || [],
|
|
140
|
+
});
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error('Failed to send message:', error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async handleWidgetAction(event: { actionName: string; payload: any }) {
|
|
147
|
+
console.log('Widget action:', event);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetch('/api/chat/action', {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
headers: { 'Content-Type': 'application/json' },
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
action: event.actionName,
|
|
155
|
+
payload: event.payload,
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const data = await response.json();
|
|
160
|
+
|
|
161
|
+
// Add response with new widgets
|
|
162
|
+
this.messages.push({
|
|
163
|
+
id: this.messageId++,
|
|
164
|
+
content: data.content || '',
|
|
165
|
+
widgets: data.widgets || [],
|
|
166
|
+
});
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error('Failed to handle widget action:', error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Using Custom Widgets
|
|
175
|
+
|
|
176
|
+
### 1. Define your custom widget
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// custom-widgets/weather-widget.ts
|
|
180
|
+
import { ChatWidget } from '@bbq-chat/widgets-angular';
|
|
181
|
+
|
|
182
|
+
export class WeatherWidget extends ChatWidget {
|
|
183
|
+
override type = 'weather';
|
|
184
|
+
|
|
185
|
+
constructor(
|
|
186
|
+
public label: string,
|
|
187
|
+
public action: string,
|
|
188
|
+
public city: string,
|
|
189
|
+
public temperature: number,
|
|
190
|
+
public condition: string
|
|
191
|
+
) {
|
|
192
|
+
super(label, action);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 2. Register the custom widget
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// app.config.ts or component
|
|
201
|
+
import { Component, OnInit } from '@angular/core';
|
|
202
|
+
import { WidgetRegistryService } from '@bbq-chat/widgets-angular';
|
|
203
|
+
import { WeatherWidget } from './custom-widgets/weather-widget';
|
|
204
|
+
|
|
205
|
+
@Component({
|
|
206
|
+
selector: 'app-root',
|
|
207
|
+
template: '...'
|
|
208
|
+
})
|
|
209
|
+
export class AppComponent implements OnInit {
|
|
210
|
+
constructor(private widgetRegistry: WidgetRegistryService) {}
|
|
211
|
+
|
|
212
|
+
ngOnInit() {
|
|
213
|
+
// Register custom widget factory
|
|
214
|
+
this.widgetRegistry.registerFactory('weather', (obj: any) => {
|
|
215
|
+
if (obj.type === 'weather') {
|
|
216
|
+
return new WeatherWidget(
|
|
217
|
+
obj.label,
|
|
218
|
+
obj.action,
|
|
219
|
+
obj.city,
|
|
220
|
+
obj.temperature,
|
|
221
|
+
obj.condition
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 3. Create custom renderers
|
|
231
|
+
|
|
232
|
+
The library now supports three types of custom renderers:
|
|
233
|
+
|
|
234
|
+
#### Option A: HTML Function Renderer
|
|
235
|
+
|
|
236
|
+
Use a function that returns HTML strings:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { Component, OnInit } from '@angular/core';
|
|
240
|
+
import { WidgetRegistryService } from '@bbq-chat/widgets-angular';
|
|
241
|
+
import { WeatherWidget } from './custom-widgets/weather-widget';
|
|
242
|
+
|
|
243
|
+
@Component({
|
|
244
|
+
selector: 'app-root',
|
|
245
|
+
template: '...'
|
|
246
|
+
})
|
|
247
|
+
export class AppComponent implements OnInit {
|
|
248
|
+
constructor(private widgetRegistry: WidgetRegistryService) {}
|
|
249
|
+
|
|
250
|
+
ngOnInit() {
|
|
251
|
+
// Register custom widget factory
|
|
252
|
+
this.widgetRegistry.registerFactory('weather', (obj: any) => {
|
|
253
|
+
if (obj.type === 'weather') {
|
|
254
|
+
return new WeatherWidget(
|
|
255
|
+
obj.label,
|
|
256
|
+
obj.action,
|
|
257
|
+
obj.city,
|
|
258
|
+
obj.temperature,
|
|
259
|
+
obj.condition
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Register HTML renderer
|
|
266
|
+
this.widgetRegistry.registerRenderer('weather', (widget) => {
|
|
267
|
+
const w = widget as WeatherWidget;
|
|
268
|
+
return `
|
|
269
|
+
<div class="weather-widget">
|
|
270
|
+
<h3>${w.city}</h3>
|
|
271
|
+
<p>${w.temperature}°C - ${w.condition}</p>
|
|
272
|
+
</div>
|
|
273
|
+
`;
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Option B: Angular Component Renderer (Recommended)
|
|
280
|
+
|
|
281
|
+
Create an Angular component for better type safety and data binding:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// weather-widget.component.ts
|
|
285
|
+
import { Component, Input } from '@angular/core';
|
|
286
|
+
import { CommonModule } from '@angular/common';
|
|
287
|
+
import { CustomWidgetComponent } from '@bbq-chat/widgets-angular';
|
|
288
|
+
import { WeatherWidget } from './custom-widgets/weather-widget';
|
|
289
|
+
|
|
290
|
+
@Component({
|
|
291
|
+
selector: 'app-weather-widget',
|
|
292
|
+
standalone: true,
|
|
293
|
+
imports: [CommonModule],
|
|
294
|
+
template: `
|
|
295
|
+
<div class="weather-widget">
|
|
296
|
+
<h3>{{ weatherWidget.city }}</h3>
|
|
297
|
+
<div class="weather-info">
|
|
298
|
+
<p class="temperature">{{ weatherWidget.temperature }}°C</p>
|
|
299
|
+
<p class="condition">{{ weatherWidget.condition }}</p>
|
|
300
|
+
</div>
|
|
301
|
+
<button (click)="onRefresh()">Refresh</button>
|
|
302
|
+
</div>
|
|
303
|
+
`,
|
|
304
|
+
styles: [`
|
|
305
|
+
.weather-widget {
|
|
306
|
+
padding: 1rem;
|
|
307
|
+
border: 1px solid #ccc;
|
|
308
|
+
border-radius: 8px;
|
|
309
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
310
|
+
color: white;
|
|
311
|
+
}
|
|
312
|
+
.temperature {
|
|
313
|
+
font-size: 2rem;
|
|
314
|
+
font-weight: bold;
|
|
315
|
+
}
|
|
316
|
+
`]
|
|
317
|
+
})
|
|
318
|
+
export class WeatherWidgetComponent implements CustomWidgetComponent {
|
|
319
|
+
@Input() widget!: ChatWidget;
|
|
320
|
+
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
321
|
+
|
|
322
|
+
get weatherWidget(): WeatherWidget {
|
|
323
|
+
return this.widget as WeatherWidget;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
onRefresh() {
|
|
327
|
+
if (this.widgetAction) {
|
|
328
|
+
this.widgetAction('refresh_weather', {
|
|
329
|
+
city: this.weatherWidget.city
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Then register the component:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { Component, OnInit } from '@angular/core';
|
|
340
|
+
import { WidgetRegistryService } from '@bbq-chat/widgets-angular';
|
|
341
|
+
import { WeatherWidget } from './custom-widgets/weather-widget';
|
|
342
|
+
import { WeatherWidgetComponent } from './weather-widget.component';
|
|
343
|
+
|
|
344
|
+
@Component({
|
|
345
|
+
selector: 'app-root',
|
|
346
|
+
template: '...'
|
|
347
|
+
})
|
|
348
|
+
export class AppComponent implements OnInit {
|
|
349
|
+
constructor(private widgetRegistry: WidgetRegistryService) {}
|
|
350
|
+
|
|
351
|
+
ngOnInit() {
|
|
352
|
+
// Register custom widget factory
|
|
353
|
+
this.widgetRegistry.registerFactory('weather', (obj: any) => {
|
|
354
|
+
if (obj.type === 'weather') {
|
|
355
|
+
return new WeatherWidget(
|
|
356
|
+
obj.label,
|
|
357
|
+
obj.action,
|
|
358
|
+
obj.city,
|
|
359
|
+
obj.temperature,
|
|
360
|
+
obj.condition
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Register component renderer - enables full Angular features
|
|
367
|
+
this.widgetRegistry.registerRenderer('weather', WeatherWidgetComponent);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
#### Option C: Angular Template Renderer
|
|
373
|
+
|
|
374
|
+
Use Angular templates for inline rendering with data binding:
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
import { Component, OnInit, ViewChild, TemplateRef } from '@angular/core';
|
|
378
|
+
import { WidgetRegistryService, WidgetTemplateContext } from '@bbq-chat/widgets-angular';
|
|
379
|
+
import { WeatherWidget } from './custom-widgets/weather-widget';
|
|
380
|
+
|
|
381
|
+
@Component({
|
|
382
|
+
selector: 'app-root',
|
|
383
|
+
template: `
|
|
384
|
+
<ng-template #weatherTemplate let-widget let-emitAction="emitAction">
|
|
385
|
+
<div class="weather-widget">
|
|
386
|
+
<h3>{{ widget.city }}</h3>
|
|
387
|
+
<p>{{ widget.temperature }}°C - {{ widget.condition }}</p>
|
|
388
|
+
<button (click)="emitAction('refresh_weather', { city: widget.city })">
|
|
389
|
+
Refresh
|
|
390
|
+
</button>
|
|
391
|
+
</div>
|
|
392
|
+
</ng-template>
|
|
393
|
+
|
|
394
|
+
<bbq-widget-renderer
|
|
395
|
+
[widgets]="widgets"
|
|
396
|
+
(widgetAction)="handleWidgetAction($event)">
|
|
397
|
+
</bbq-widget-renderer>
|
|
398
|
+
`
|
|
399
|
+
})
|
|
400
|
+
export class AppComponent implements OnInit {
|
|
401
|
+
@ViewChild('weatherTemplate', { static: true })
|
|
402
|
+
weatherTemplate!: TemplateRef<WidgetTemplateContext>;
|
|
403
|
+
|
|
404
|
+
widgets: ChatWidget[] = [];
|
|
405
|
+
|
|
406
|
+
constructor(private widgetRegistry: WidgetRegistryService) {}
|
|
407
|
+
|
|
408
|
+
ngOnInit() {
|
|
409
|
+
// Register custom widget factory
|
|
410
|
+
this.widgetRegistry.registerFactory('weather', (obj: any) => {
|
|
411
|
+
if (obj.type === 'weather') {
|
|
412
|
+
return new WeatherWidget(
|
|
413
|
+
obj.label,
|
|
414
|
+
obj.action,
|
|
415
|
+
obj.city,
|
|
416
|
+
obj.temperature,
|
|
417
|
+
obj.condition
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// Register template renderer - enables inline Angular templates
|
|
424
|
+
this.widgetRegistry.registerRenderer('weather', this.weatherTemplate);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
handleWidgetAction(event: { actionName: string; payload: any }) {
|
|
428
|
+
console.log('Widget action:', event);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Benefits of Component-Based Renderers
|
|
434
|
+
|
|
435
|
+
Using Angular components for custom widgets provides several advantages:
|
|
436
|
+
|
|
437
|
+
1. **Type Safety**: Full TypeScript support with interfaces and types
|
|
438
|
+
2. **Data Binding**: Use Angular's powerful two-way data binding
|
|
439
|
+
3. **Lifecycle Hooks**: Access to Angular lifecycle hooks (ngOnInit, ngOnDestroy, etc.)
|
|
440
|
+
4. **Change Detection**: Automatic UI updates when data changes
|
|
441
|
+
5. **Dependency Injection**: Inject services directly into widget components
|
|
442
|
+
6. **Animations**: Use Angular animations for smooth transitions
|
|
443
|
+
7. **Reactive Forms**: Integrate with Angular reactive forms
|
|
444
|
+
8. **Testing**: Easier unit testing with Angular TestBed
|
|
445
|
+
|
|
446
|
+
## Integration with Forms
|
|
447
|
+
|
|
448
|
+
The package automatically handles form submissions. Example:
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
handleWidgetAction(event: { actionName: string; payload: any }) {
|
|
452
|
+
if (event.actionName === 'submit_form') {
|
|
453
|
+
console.log('Form data:', event.payload);
|
|
454
|
+
// payload contains all form field values
|
|
455
|
+
// { name: 'John', email: 'john@example.com', ... }
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## Theming
|
|
461
|
+
|
|
462
|
+
Import different themes:
|
|
463
|
+
|
|
464
|
+
```css
|
|
465
|
+
/* Light theme (default) */
|
|
466
|
+
@import '@bbq-chat/widgets/styles/light';
|
|
467
|
+
|
|
468
|
+
/* Dark theme */
|
|
469
|
+
@import '@bbq-chat/widgets/styles/dark';
|
|
470
|
+
|
|
471
|
+
/* Corporate theme */
|
|
472
|
+
@import '@bbq-chat/widgets/styles/corporate';
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Or customize with CSS variables:
|
|
476
|
+
|
|
477
|
+
```css
|
|
478
|
+
:root {
|
|
479
|
+
--bbq-primary-color: #007bff;
|
|
480
|
+
--bbq-secondary-color: #6c757d;
|
|
481
|
+
--bbq-border-radius: 8px;
|
|
482
|
+
/* ... other variables */
|
|
483
|
+
}
|
|
484
|
+
```
|
package/README.md
CHANGED
|
@@ -131,6 +131,125 @@ export class AppComponent implements OnInit {
|
|
|
131
131
|
}
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
### FormValidationService
|
|
135
|
+
|
|
136
|
+
Service for monitoring and debugging form validation events. This is useful for:
|
|
137
|
+
- Debugging form validation logic across the application
|
|
138
|
+
- Showing validation state in a centralized UI component
|
|
139
|
+
- Building custom validation handling workflows
|
|
140
|
+
|
|
141
|
+
The service emits validation events whenever a form is validated, allowing any component in your application to subscribe to validation state changes without direct access to a specific form instance.
|
|
142
|
+
|
|
143
|
+
**API:**
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
interface FormValidationEvent {
|
|
147
|
+
formId: string;
|
|
148
|
+
valid: boolean;
|
|
149
|
+
errors: Array<{ field: string; reason?: string }>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@Injectable({ providedIn: 'root' })
|
|
153
|
+
export class FormValidationService {
|
|
154
|
+
get validation$(): Observable<FormValidationEvent>
|
|
155
|
+
emit(event: FormValidationEvent): void
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Example - Direct Service Subscription:**
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
163
|
+
import { FormValidationService } from '@bbq-chat/widgets-angular';
|
|
164
|
+
import { Subscription } from 'rxjs';
|
|
165
|
+
|
|
166
|
+
@Component({
|
|
167
|
+
selector: 'app-validation-monitor',
|
|
168
|
+
template: `
|
|
169
|
+
<div *ngIf="lastEvent" class="validation-report">
|
|
170
|
+
<p>Form: {{ lastEvent.formId }}</p>
|
|
171
|
+
<p>Valid: {{ lastEvent.valid }}</p>
|
|
172
|
+
<ul *ngIf="lastEvent.errors.length > 0">
|
|
173
|
+
<li *ngFor="let error of lastEvent.errors">
|
|
174
|
+
{{ error.field }}: {{ error.reason }}
|
|
175
|
+
</li>
|
|
176
|
+
</ul>
|
|
177
|
+
</div>
|
|
178
|
+
`
|
|
179
|
+
})
|
|
180
|
+
export class ValidationMonitorComponent implements OnInit, OnDestroy {
|
|
181
|
+
lastEvent: any;
|
|
182
|
+
private sub?: Subscription;
|
|
183
|
+
|
|
184
|
+
constructor(private formValidationService: FormValidationService) {}
|
|
185
|
+
|
|
186
|
+
ngOnInit() {
|
|
187
|
+
this.sub = this.formValidationService.validation$.subscribe(event => {
|
|
188
|
+
this.lastEvent = event;
|
|
189
|
+
console.log('Form validation state:', event);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
ngOnDestroy() {
|
|
194
|
+
this.sub?.unsubscribe();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Example - Using FormValidationListenerComponent:**
|
|
200
|
+
|
|
201
|
+
For a quick solution, use the built-in `FormValidationListenerComponent`:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { Component } from '@angular/core';
|
|
205
|
+
import { FormValidationListenerComponent } from '@bbq-chat/widgets-angular';
|
|
206
|
+
|
|
207
|
+
@Component({
|
|
208
|
+
selector: 'app-root',
|
|
209
|
+
standalone: true,
|
|
210
|
+
imports: [FormValidationListenerComponent],
|
|
211
|
+
template: `
|
|
212
|
+
<!-- Optional: filter by formId, or omit to see all validation events -->
|
|
213
|
+
<bbq-form-validation-listener [formId]="'my-form-id'"></bbq-form-validation-listener>
|
|
214
|
+
`
|
|
215
|
+
})
|
|
216
|
+
export class AppComponent {}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Overriding Form Field Components:**
|
|
220
|
+
|
|
221
|
+
You can also override the components used to render form fields by passing a registry override to `FormWidgetComponent`:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { Component } from '@angular/core';
|
|
225
|
+
import { FormWidgetComponent, CustomWidgetComponent } from '@bbq-chat/widgets-angular';
|
|
226
|
+
import { MyCustomInputComponent } from './my-custom-input.component';
|
|
227
|
+
|
|
228
|
+
@Component({
|
|
229
|
+
selector: 'app-form-container',
|
|
230
|
+
standalone: true,
|
|
231
|
+
imports: [FormWidgetComponent],
|
|
232
|
+
template: `
|
|
233
|
+
<bbq-form-widget
|
|
234
|
+
[widget]="formWidget"
|
|
235
|
+
[fieldComponentRegistryOverride]="fieldRegistry"
|
|
236
|
+
(validationState)="onValidation($event)">
|
|
237
|
+
</bbq-form-widget>
|
|
238
|
+
`
|
|
239
|
+
})
|
|
240
|
+
export class FormContainerComponent {
|
|
241
|
+
formWidget: any;
|
|
242
|
+
fieldRegistry = {
|
|
243
|
+
'input': MyCustomInputComponent,
|
|
244
|
+
// Add other overrides as needed
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
onValidation(event: { valid: boolean; errors: any[] }) {
|
|
248
|
+
console.log('Form validation:', event);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
134
253
|
## Widget Types
|
|
135
254
|
|
|
136
255
|
All standard widget types from `@bbq-chat/widgets` are supported and rendered as native Angular components:
|
package/angular.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"cli": {
|
|
5
|
+
"packageManager": "npm"
|
|
6
|
+
},
|
|
7
|
+
"newProjectRoot": "projects",
|
|
8
|
+
"projects": {
|
|
9
|
+
"js-angular": {
|
|
10
|
+
"projectType": "library",
|
|
11
|
+
"root": "./",
|
|
12
|
+
"sourceRoot": "src",
|
|
13
|
+
"prefix": "lib",
|
|
14
|
+
"architect": {
|
|
15
|
+
"build": {
|
|
16
|
+
"builder": "@angular/build:ng-packagr",
|
|
17
|
+
"configurations": {
|
|
18
|
+
"production": {
|
|
19
|
+
"tsConfig": "tsconfig.lib.prod.json"
|
|
20
|
+
},
|
|
21
|
+
"development": {
|
|
22
|
+
"tsConfig": "tsconfig.lib.json"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"defaultConfiguration": "production"
|
|
26
|
+
},
|
|
27
|
+
"test": {
|
|
28
|
+
"builder": "@angular/build:unit-test",
|
|
29
|
+
"options": {
|
|
30
|
+
"tsConfig": "tsconfig.spec.json"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
package/ng-package.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bbq-chat/widgets-angular",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Angular components and services for BbQ ChatWidgets",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "BbQ ChatWidgets Contributors",
|
|
@@ -22,6 +22,15 @@
|
|
|
22
22
|
"bbq-chat"
|
|
23
23
|
],
|
|
24
24
|
"sideEffects": false,
|
|
25
|
+
"scripts": {
|
|
26
|
+
"ng": "ng",
|
|
27
|
+
"start": "ng serve",
|
|
28
|
+
"build": "ng build",
|
|
29
|
+
"watch": "ng build --watch --configuration development",
|
|
30
|
+
"test": "ng test",
|
|
31
|
+
"prepublishOnly": "npm run build && node -e \"require('fs').cpSync('README.md', 'dist/README.md'); require('fs').cpSync('LICENSE', 'dist/LICENSE', {force:false})\" || exit 0",
|
|
32
|
+
"publish:lib": "cd dist && npm publish"
|
|
33
|
+
},
|
|
25
34
|
"packageManager": "npm@10.9.0",
|
|
26
35
|
"peerDependencies": {
|
|
27
36
|
"@angular/common": ">=17.0.0",
|
|
@@ -33,18 +42,14 @@
|
|
|
33
42
|
"rxjs": "~7.8.0",
|
|
34
43
|
"@bbq-chat/widgets": "^1.0.0"
|
|
35
44
|
},
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
"dependencies": {
|
|
48
|
-
"tslib": "^2.3.0"
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@angular/build": "^21.0.5",
|
|
47
|
+
"@angular/cli": "^21.0.5",
|
|
48
|
+
"@angular/compiler-cli": "^21.0.0",
|
|
49
|
+
"@bbq-chat/widgets": "file:../js",
|
|
50
|
+
"jsdom": "^27.1.0",
|
|
51
|
+
"ng-packagr": "^21.0.0",
|
|
52
|
+
"typescript": "~5.9.2",
|
|
53
|
+
"vitest": "^4.0.8"
|
|
49
54
|
}
|
|
50
|
-
}
|
|
55
|
+
}
|