@bbq-chat/widgets-angular 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/fesm2022/index.mjs +2152 -0
  2. package/fesm2022/index.mjs.map +1 -0
  3. package/package.json +15 -20
  4. package/types/index.d.ts +660 -0
  5. package/.angular/cache/21.0.5/ng-packagr/97cbacd0e5e4cb18d1fead4d7f3aee1c3863ba3ffbe7cb7dd7780f237a848a5c +0 -1
  6. package/.angular/cache/21.0.5/ng-packagr/tsbuildinfo/index.tsbuildinfo +0 -1
  7. package/.eslintrc.json +0 -23
  8. package/.prettierrc.json +0 -8
  9. package/EXAMPLES.md +0 -484
  10. package/angular.json +0 -36
  11. package/ng-package.json +0 -9
  12. package/src/angular-widget-renderer.spec.ts +0 -157
  13. package/src/components/button.component.ts +0 -35
  14. package/src/components/card.component.ts +0 -52
  15. package/src/components/datepicker.component.ts +0 -63
  16. package/src/components/dropdown.component.ts +0 -65
  17. package/src/components/fileupload.component.ts +0 -71
  18. package/src/components/form.component.ts +0 -433
  19. package/src/components/image.component.ts +0 -33
  20. package/src/components/imagecollection.component.ts +0 -39
  21. package/src/components/index.ts +0 -20
  22. package/src/components/input.component.ts +0 -63
  23. package/src/components/multiselect.component.ts +0 -67
  24. package/src/components/progressbar.component.ts +0 -50
  25. package/src/components/slider.component.ts +0 -67
  26. package/src/components/textarea.component.ts +0 -63
  27. package/src/components/themeswitcher.component.ts +0 -46
  28. package/src/components/toggle.component.ts +0 -63
  29. package/src/custom-widget-renderer.types.ts +0 -120
  30. package/src/examples/form-validation-listener.component.ts +0 -41
  31. package/src/public_api.ts +0 -107
  32. package/src/renderers/AngularWidgetRenderer.ts +0 -100
  33. package/src/renderers/built-in-components.ts +0 -41
  34. package/src/renderers/index.ts +0 -7
  35. package/src/services/form-validation.service.ts +0 -21
  36. package/src/widget-di.tokens.ts +0 -95
  37. package/src/widget-registry.service.ts +0 -128
  38. package/src/widget-renderer.component.ts +0 -421
  39. package/tsconfig.json +0 -37
  40. package/tsconfig.lib.json +0 -18
  41. package/tsconfig.lib.prod.json +0 -11
  42. package/tsconfig.spec.json +0 -13
@@ -0,0 +1,2152 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Input, Component, Injectable, EventEmitter, createComponent, ViewContainerRef, ViewChildren, Output, InjectionToken, ViewChild, Inject, Optional } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule, JsonPipe } from '@angular/common';
5
+ import * as i2$1 from '@bbq-chat/widgets';
6
+ import { WidgetEventManager, SsrWidgetRenderer, customWidgetRegistry } from '@bbq-chat/widgets';
7
+ export { ChatWidget, SsrWidgetRenderer, WidgetEventManager, customWidgetRegistry } from '@bbq-chat/widgets';
8
+ import * as i2 from '@angular/forms';
9
+ import { FormsModule } from '@angular/forms';
10
+ import { Subject } from 'rxjs';
11
+
12
+ /**
13
+ * Type guard to check if a renderer is a TemplateRef
14
+ */
15
+ function isTemplateRenderer(renderer) {
16
+ return (renderer !== null &&
17
+ typeof renderer === 'object' &&
18
+ 'createEmbeddedView' in renderer);
19
+ }
20
+ /**
21
+ * Type guard to check if a renderer is an Angular Component
22
+ *
23
+ * Note: This uses a heuristic check based on the following assumptions:
24
+ * 1. Components are constructor functions
25
+ * 2. Components have a prototype with a constructor property
26
+ * 3. Components typically use dependency injection (no required constructor params)
27
+ *
28
+ * Limitation: This may not detect components with required constructor parameters.
29
+ * For edge cases, explicitly check your component's constructor signature.
30
+ *
31
+ * Alternative: You can always register a wrapper component that has no required params.
32
+ */
33
+ function isComponentRenderer(renderer) {
34
+ // Check if it's a function (constructor) but not a regular function renderer
35
+ if (typeof renderer !== 'function') {
36
+ return false;
37
+ }
38
+ // Check for Angular component characteristics
39
+ // Components typically have prototype with constructor property
40
+ return (renderer.prototype !== undefined &&
41
+ renderer.prototype.constructor === renderer &&
42
+ renderer.length === 0 // Constructor with no required params (Angular DI)
43
+ );
44
+ }
45
+ /**
46
+ * Type guard to check if a renderer is an HTML function
47
+ *
48
+ * Note: This should be checked AFTER checking for component and template renderers
49
+ * since components are also functions but with additional properties.
50
+ */
51
+ function isHtmlRenderer(renderer) {
52
+ return typeof renderer === 'function';
53
+ }
54
+
55
+ /**
56
+ * Angular widget renderer
57
+ * Returns Angular component types for dynamic rendering
58
+ * Provides feature parity with SsrWidgetRenderer but uses Angular components
59
+ */
60
+ class AngularWidgetRenderer {
61
+ framework = 'Angular';
62
+ overrides;
63
+ componentRegistry = new Map();
64
+ constructor(options) {
65
+ this.overrides = options?.components;
66
+ }
67
+ /**
68
+ * Register all built-in widget components
69
+ * Must be called after components are imported to avoid circular dependencies
70
+ */
71
+ registerBuiltInComponents(components) {
72
+ for (const [type, component] of Object.entries(components)) {
73
+ this.componentRegistry.set(type, component);
74
+ }
75
+ }
76
+ /**
77
+ * Register or override a widget component
78
+ * Use this to replace built-in components or add custom ones
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * renderer.registerComponent('button', MyCustomButtonComponent);
83
+ * ```
84
+ */
85
+ registerComponent(type, component) {
86
+ this.componentRegistry.set(type, component);
87
+ }
88
+ /**
89
+ * Register multiple widget components at once
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * renderer.registerComponents({
94
+ * button: MyButtonComponent,
95
+ * card: MyCardComponent
96
+ * });
97
+ * ```
98
+ */
99
+ registerComponents(components) {
100
+ for (const [type, component] of Object.entries(components)) {
101
+ this.componentRegistry.set(type, component);
102
+ }
103
+ }
104
+ /**
105
+ * Get the Angular component type for a given widget
106
+ * Returns the component class that should be dynamically instantiated
107
+ */
108
+ getComponentType(widget) {
109
+ const type = widget.type;
110
+ // Check for custom override first
111
+ if (this.overrides && this.overrides[type]) {
112
+ return this.overrides[type];
113
+ }
114
+ // Check built-in registry
115
+ if (this.componentRegistry.has(type)) {
116
+ return this.componentRegistry.get(type);
117
+ }
118
+ return null;
119
+ }
120
+ /**
121
+ * Legacy method for IWidgetRenderer interface compatibility
122
+ * Not used in Angular rendering but required by interface
123
+ * @deprecated Use getComponentType() instead for Angular rendering
124
+ */
125
+ renderWidget(widget) {
126
+ // This method is not used in Angular rendering
127
+ // It's only here for interface compatibility
128
+ return `<!-- Angular component rendering for ${widget.type} -->`;
129
+ }
130
+ }
131
+
132
+ class ButtonWidgetComponent {
133
+ widget;
134
+ widgetAction;
135
+ get buttonWidget() {
136
+ return this.widget;
137
+ }
138
+ onClick() {
139
+ if (this.widgetAction) {
140
+ this.widgetAction(this.buttonWidget.action, {});
141
+ }
142
+ }
143
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ButtonWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
144
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: ButtonWidgetComponent, isStandalone: true, selector: "bbq-button-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
145
+ <button
146
+ class="bbq-widget bbq-button"
147
+ [attr.data-widget-type]="'button'"
148
+ [attr.data-action]="buttonWidget.action"
149
+ type="button"
150
+ (click)="onClick()">
151
+ {{ buttonWidget.label }}
152
+ </button>
153
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
154
+ }
155
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ButtonWidgetComponent, decorators: [{
156
+ type: Component,
157
+ args: [{ selector: 'bbq-button-widget', standalone: true, imports: [CommonModule], template: `
158
+ <button
159
+ class="bbq-widget bbq-button"
160
+ [attr.data-widget-type]="'button'"
161
+ [attr.data-action]="buttonWidget.action"
162
+ type="button"
163
+ (click)="onClick()">
164
+ {{ buttonWidget.label }}
165
+ </button>
166
+ ` }]
167
+ }], propDecorators: { widget: [{
168
+ type: Input
169
+ }] } });
170
+
171
+ class CardWidgetComponent {
172
+ widget;
173
+ widgetAction;
174
+ get cardWidget() {
175
+ return this.widget;
176
+ }
177
+ onClick() {
178
+ if (this.widgetAction) {
179
+ this.widgetAction(this.cardWidget.action, {});
180
+ }
181
+ }
182
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: CardWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
183
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: CardWidgetComponent, isStandalone: true, selector: "bbq-card-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
184
+ <div
185
+ class="bbq-widget bbq-card"
186
+ [attr.data-widget-type]="'card'"
187
+ [attr.data-action]="cardWidget.action"
188
+ role="article">
189
+ <h3 class="bbq-card-title">{{ cardWidget.title }}</h3>
190
+ @if (cardWidget.description) {
191
+ <p class="bbq-card-description">{{ cardWidget.description }}</p>
192
+ }
193
+ @if (cardWidget.imageUrl) {
194
+ <img
195
+ class="bbq-card-image"
196
+ [src]="cardWidget.imageUrl"
197
+ [alt]="cardWidget.title"
198
+ loading="lazy"
199
+ style="display:block;max-width:100%;height:auto;object-fit:cover;max-height:200px;border-radius:6px;margin-bottom:12px;" />
200
+ }
201
+ <button
202
+ class="bbq-card-action bbq-button"
203
+ [attr.data-action]="cardWidget.action"
204
+ type="button"
205
+ (click)="onClick()">
206
+ {{ cardWidget.label }}
207
+ </button>
208
+ </div>
209
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
210
+ }
211
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: CardWidgetComponent, decorators: [{
212
+ type: Component,
213
+ args: [{ selector: 'bbq-card-widget', standalone: true, imports: [CommonModule], template: `
214
+ <div
215
+ class="bbq-widget bbq-card"
216
+ [attr.data-widget-type]="'card'"
217
+ [attr.data-action]="cardWidget.action"
218
+ role="article">
219
+ <h3 class="bbq-card-title">{{ cardWidget.title }}</h3>
220
+ @if (cardWidget.description) {
221
+ <p class="bbq-card-description">{{ cardWidget.description }}</p>
222
+ }
223
+ @if (cardWidget.imageUrl) {
224
+ <img
225
+ class="bbq-card-image"
226
+ [src]="cardWidget.imageUrl"
227
+ [alt]="cardWidget.title"
228
+ loading="lazy"
229
+ style="display:block;max-width:100%;height:auto;object-fit:cover;max-height:200px;border-radius:6px;margin-bottom:12px;" />
230
+ }
231
+ <button
232
+ class="bbq-card-action bbq-button"
233
+ [attr.data-action]="cardWidget.action"
234
+ type="button"
235
+ (click)="onClick()">
236
+ {{ cardWidget.label }}
237
+ </button>
238
+ </div>
239
+ ` }]
240
+ }], propDecorators: { widget: [{
241
+ type: Input
242
+ }] } });
243
+
244
+ class InputWidgetComponent {
245
+ widget;
246
+ widgetAction;
247
+ value = '';
248
+ inputId = '';
249
+ get inputWidget() {
250
+ return this.widget;
251
+ }
252
+ get showLabel() {
253
+ const widget = this.inputWidget;
254
+ if (widget.hideLabel === true) {
255
+ return false;
256
+ }
257
+ if (widget.showLabel === false) {
258
+ return false;
259
+ }
260
+ return true;
261
+ }
262
+ get inputClasses() {
263
+ return this.isFormAppearance ? ['bbq-form-input'] : ['bbq-input'];
264
+ }
265
+ get isFormAppearance() {
266
+ return this.inputWidget.appearance === 'form';
267
+ }
268
+ ngOnInit() {
269
+ this.inputId = `bbq-${this.inputWidget.action.replace(/\s+/g, '-').toLowerCase()}-input`;
270
+ this.value = this.inputWidget.defaultValue ?? '';
271
+ }
272
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: InputWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
273
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: InputWidgetComponent, isStandalone: true, selector: "bbq-input-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
274
+ <div
275
+ class="bbq-widget bbq-input"
276
+ [attr.data-widget-type]="'input'">
277
+ <label *ngIf="showLabel" class="bbq-input-label" [attr.for]="inputId">
278
+ {{ inputWidget.label }}
279
+ </label>
280
+ <input
281
+ type="text"
282
+ [id]="inputId"
283
+ [ngClass]="inputClasses"
284
+ [attr.data-action]="inputWidget.action"
285
+ [placeholder]="inputWidget.placeholder || ''"
286
+ [maxLength]="inputWidget.maxLength || 0"
287
+ [(ngModel)]="value" />
288
+ </div>
289
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
290
+ }
291
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: InputWidgetComponent, decorators: [{
292
+ type: Component,
293
+ args: [{ selector: 'bbq-input-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
294
+ <div
295
+ class="bbq-widget bbq-input"
296
+ [attr.data-widget-type]="'input'">
297
+ <label *ngIf="showLabel" class="bbq-input-label" [attr.for]="inputId">
298
+ {{ inputWidget.label }}
299
+ </label>
300
+ <input
301
+ type="text"
302
+ [id]="inputId"
303
+ [ngClass]="inputClasses"
304
+ [attr.data-action]="inputWidget.action"
305
+ [placeholder]="inputWidget.placeholder || ''"
306
+ [maxLength]="inputWidget.maxLength || 0"
307
+ [(ngModel)]="value" />
308
+ </div>
309
+ ` }]
310
+ }], propDecorators: { widget: [{
311
+ type: Input
312
+ }] } });
313
+
314
+ class TextAreaWidgetComponent {
315
+ widget;
316
+ widgetAction;
317
+ value = '';
318
+ textareaId = '';
319
+ get textareaWidget() {
320
+ return this.widget;
321
+ }
322
+ get showLabel() {
323
+ const widget = this.textareaWidget;
324
+ if (widget.hideLabel === true) {
325
+ return false;
326
+ }
327
+ if (widget.showLabel === false) {
328
+ return false;
329
+ }
330
+ return true;
331
+ }
332
+ get textareaClasses() {
333
+ return this.isFormAppearance ? ['bbq-form-textarea'] : ['bbq-form-textarea', 'bbq-input'];
334
+ }
335
+ get isFormAppearance() {
336
+ return this.textareaWidget.appearance === 'form';
337
+ }
338
+ ngOnInit() {
339
+ this.textareaId = `bbq-${this.textareaWidget.action.replace(/\s+/g, '-').toLowerCase()}-textarea`;
340
+ this.value = this.textareaWidget.defaultValue ?? '';
341
+ }
342
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: TextAreaWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
343
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: TextAreaWidgetComponent, isStandalone: true, selector: "bbq-textarea-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
344
+ <div
345
+ class="bbq-widget bbq-textarea"
346
+ [attr.data-widget-type]="'textarea'">
347
+ <label *ngIf="showLabel" class="bbq-textarea-label" [attr.for]="textareaId">
348
+ {{ textareaWidget.label }}
349
+ </label>
350
+ <textarea
351
+ [id]="textareaId"
352
+ [ngClass]="textareaClasses"
353
+ [attr.data-action]="textareaWidget.action"
354
+ [placeholder]="textareaWidget.placeholder || ''"
355
+ [maxLength]="textareaWidget.maxLength || 0"
356
+ [rows]="textareaWidget.rows || 4"
357
+ [(ngModel)]="value"></textarea>
358
+ </div>
359
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
360
+ }
361
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: TextAreaWidgetComponent, decorators: [{
362
+ type: Component,
363
+ args: [{ selector: 'bbq-textarea-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
364
+ <div
365
+ class="bbq-widget bbq-textarea"
366
+ [attr.data-widget-type]="'textarea'">
367
+ <label *ngIf="showLabel" class="bbq-textarea-label" [attr.for]="textareaId">
368
+ {{ textareaWidget.label }}
369
+ </label>
370
+ <textarea
371
+ [id]="textareaId"
372
+ [ngClass]="textareaClasses"
373
+ [attr.data-action]="textareaWidget.action"
374
+ [placeholder]="textareaWidget.placeholder || ''"
375
+ [maxLength]="textareaWidget.maxLength || 0"
376
+ [rows]="textareaWidget.rows || 4"
377
+ [(ngModel)]="value"></textarea>
378
+ </div>
379
+ ` }]
380
+ }], propDecorators: { widget: [{
381
+ type: Input
382
+ }] } });
383
+
384
+ class DropdownWidgetComponent {
385
+ widget;
386
+ widgetAction;
387
+ value = '';
388
+ selectId = '';
389
+ get dropdownWidget() {
390
+ return this.widget;
391
+ }
392
+ get showLabel() {
393
+ const widget = this.dropdownWidget;
394
+ if (widget.hideLabel === true) {
395
+ return false;
396
+ }
397
+ if (widget.showLabel === false) {
398
+ return false;
399
+ }
400
+ return true;
401
+ }
402
+ get selectClasses() {
403
+ return this.isFormAppearance ? ['bbq-form-select'] : ['bbq-dropdown'];
404
+ }
405
+ get isFormAppearance() {
406
+ return this.dropdownWidget.appearance === 'form';
407
+ }
408
+ ngOnInit() {
409
+ this.selectId = `bbq-${this.dropdownWidget.action.replace(/\s+/g, '-').toLowerCase()}-select`;
410
+ this.value = this.dropdownWidget.defaultValue ?? this.dropdownWidget.options[0] ?? '';
411
+ }
412
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DropdownWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
413
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: DropdownWidgetComponent, isStandalone: true, selector: "bbq-dropdown-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
414
+ <div
415
+ class="bbq-widget bbq-dropdown"
416
+ [attr.data-widget-type]="'dropdown'">
417
+ <label *ngIf="showLabel" class="bbq-dropdown-label" [attr.for]="selectId">
418
+ {{ dropdownWidget.label }}
419
+ </label>
420
+ <select
421
+ [id]="selectId"
422
+ [ngClass]="selectClasses"
423
+ [attr.data-action]="dropdownWidget.action"
424
+ [(ngModel)]="value">
425
+ @for (option of dropdownWidget.options; track option) {
426
+ <option [value]="option">{{ option }}</option>
427
+ }
428
+ </select>
429
+ </div>
430
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
431
+ }
432
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DropdownWidgetComponent, decorators: [{
433
+ type: Component,
434
+ args: [{ selector: 'bbq-dropdown-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
435
+ <div
436
+ class="bbq-widget bbq-dropdown"
437
+ [attr.data-widget-type]="'dropdown'">
438
+ <label *ngIf="showLabel" class="bbq-dropdown-label" [attr.for]="selectId">
439
+ {{ dropdownWidget.label }}
440
+ </label>
441
+ <select
442
+ [id]="selectId"
443
+ [ngClass]="selectClasses"
444
+ [attr.data-action]="dropdownWidget.action"
445
+ [(ngModel)]="value">
446
+ @for (option of dropdownWidget.options; track option) {
447
+ <option [value]="option">{{ option }}</option>
448
+ }
449
+ </select>
450
+ </div>
451
+ ` }]
452
+ }], propDecorators: { widget: [{
453
+ type: Input
454
+ }] } });
455
+
456
+ class SliderWidgetComponent {
457
+ widget;
458
+ widgetAction;
459
+ value = 0;
460
+ sliderId = '';
461
+ get sliderWidget() {
462
+ return this.widget;
463
+ }
464
+ get showLabel() {
465
+ const widget = this.sliderWidget;
466
+ if (widget.hideLabel === true) {
467
+ return false;
468
+ }
469
+ if (widget.showLabel === false) {
470
+ return false;
471
+ }
472
+ return true;
473
+ }
474
+ get sliderClasses() {
475
+ return this.isFormAppearance ? ['bbq-form-slider'] : ['bbq-slider'];
476
+ }
477
+ get isFormAppearance() {
478
+ return this.sliderWidget.appearance === 'form';
479
+ }
480
+ ngOnInit() {
481
+ this.sliderId = `bbq-${this.sliderWidget.action.replace(/\s+/g, '-').toLowerCase()}-slider`;
482
+ this.value = this.sliderWidget.defaultValue ?? this.sliderWidget.min;
483
+ }
484
+ get displayLabel() {
485
+ return `${this.sliderWidget.label}: ${this.value}`;
486
+ }
487
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SliderWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
488
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: SliderWidgetComponent, isStandalone: true, selector: "bbq-slider-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
489
+ <div
490
+ class="bbq-widget bbq-slider"
491
+ [attr.data-widget-type]="'slider'">
492
+ <label *ngIf="showLabel" class="bbq-slider-label" [attr.for]="sliderId">
493
+ {{ displayLabel }}
494
+ </label>
495
+ <input
496
+ type="range"
497
+ [id]="sliderId"
498
+ [ngClass]="sliderClasses"
499
+ [min]="sliderWidget.min"
500
+ [max]="sliderWidget.max"
501
+ [step]="sliderWidget.step"
502
+ [attr.data-action]="sliderWidget.action"
503
+ [attr.aria-label]="sliderWidget.label"
504
+ [(ngModel)]="value" />
505
+ <span class="bbq-slider-value" aria-live="polite">{{ value }}</span>
506
+ </div>
507
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
508
+ }
509
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SliderWidgetComponent, decorators: [{
510
+ type: Component,
511
+ args: [{ selector: 'bbq-slider-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
512
+ <div
513
+ class="bbq-widget bbq-slider"
514
+ [attr.data-widget-type]="'slider'">
515
+ <label *ngIf="showLabel" class="bbq-slider-label" [attr.for]="sliderId">
516
+ {{ displayLabel }}
517
+ </label>
518
+ <input
519
+ type="range"
520
+ [id]="sliderId"
521
+ [ngClass]="sliderClasses"
522
+ [min]="sliderWidget.min"
523
+ [max]="sliderWidget.max"
524
+ [step]="sliderWidget.step"
525
+ [attr.data-action]="sliderWidget.action"
526
+ [attr.aria-label]="sliderWidget.label"
527
+ [(ngModel)]="value" />
528
+ <span class="bbq-slider-value" aria-live="polite">{{ value }}</span>
529
+ </div>
530
+ ` }]
531
+ }], propDecorators: { widget: [{
532
+ type: Input
533
+ }] } });
534
+
535
+ class ToggleWidgetComponent {
536
+ widget;
537
+ widgetAction;
538
+ checked = false;
539
+ checkboxId = '';
540
+ get toggleWidget() {
541
+ return this.widget;
542
+ }
543
+ get showLabel() {
544
+ const widget = this.toggleWidget;
545
+ if (widget.hideLabel === true) {
546
+ return false;
547
+ }
548
+ if (widget.showLabel === false) {
549
+ return false;
550
+ }
551
+ return true;
552
+ }
553
+ get checkboxClasses() {
554
+ return this.isFormAppearance ? ['bbq-toggle-input', 'bbq-form-toggle'] : ['bbq-toggle-input'];
555
+ }
556
+ get isFormAppearance() {
557
+ return this.toggleWidget.appearance === 'form';
558
+ }
559
+ ngOnInit() {
560
+ this.checkboxId = `bbq-${this.toggleWidget.action.replace(/\s+/g, '-').toLowerCase()}-checkbox`;
561
+ this.checked = this.toggleWidget.defaultValue ?? false;
562
+ }
563
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ToggleWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
564
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: ToggleWidgetComponent, isStandalone: true, selector: "bbq-toggle-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
565
+ <div
566
+ class="bbq-widget bbq-toggle"
567
+ [attr.data-widget-type]="'toggle'">
568
+ <label class="bbq-toggle-label" [attr.for]="checkboxId">
569
+ <input
570
+ type="checkbox"
571
+ [id]="checkboxId"
572
+ [ngClass]="checkboxClasses"
573
+ [attr.data-action]="toggleWidget.action"
574
+ [attr.aria-label]="toggleWidget.label"
575
+ [(ngModel)]="checked" />
576
+ <span *ngIf="showLabel" class="bbq-toggle-text">{{ toggleWidget.label }}</span>
577
+ </label>
578
+ </div>
579
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
580
+ }
581
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ToggleWidgetComponent, decorators: [{
582
+ type: Component,
583
+ args: [{ selector: 'bbq-toggle-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
584
+ <div
585
+ class="bbq-widget bbq-toggle"
586
+ [attr.data-widget-type]="'toggle'">
587
+ <label class="bbq-toggle-label" [attr.for]="checkboxId">
588
+ <input
589
+ type="checkbox"
590
+ [id]="checkboxId"
591
+ [ngClass]="checkboxClasses"
592
+ [attr.data-action]="toggleWidget.action"
593
+ [attr.aria-label]="toggleWidget.label"
594
+ [(ngModel)]="checked" />
595
+ <span *ngIf="showLabel" class="bbq-toggle-text">{{ toggleWidget.label }}</span>
596
+ </label>
597
+ </div>
598
+ ` }]
599
+ }], propDecorators: { widget: [{
600
+ type: Input
601
+ }] } });
602
+
603
+ class FileUploadWidgetComponent {
604
+ widget;
605
+ widgetAction;
606
+ inputId = '';
607
+ get fileUploadWidget() {
608
+ return this.widget;
609
+ }
610
+ get showLabel() {
611
+ const widget = this.fileUploadWidget;
612
+ if (widget.hideLabel === true) {
613
+ return false;
614
+ }
615
+ if (widget.showLabel === false) {
616
+ return false;
617
+ }
618
+ return true;
619
+ }
620
+ get inputClasses() {
621
+ return this.isFormAppearance ? ['bbq-form-fileupload'] : ['bbq-file'];
622
+ }
623
+ get isFormAppearance() {
624
+ return this.fileUploadWidget.appearance === 'form';
625
+ }
626
+ ngOnInit() {
627
+ this.inputId = `bbq-${this.fileUploadWidget.action.replace(/\s+/g, '-').toLowerCase()}-fileupload`;
628
+ }
629
+ onFileChange(event) {
630
+ const target = event.target;
631
+ if (target.files && target.files.length > 0) {
632
+ const file = target.files[0];
633
+ if (this.widgetAction) {
634
+ this.widgetAction(this.fileUploadWidget.action, { file });
635
+ }
636
+ }
637
+ }
638
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FileUploadWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
639
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: FileUploadWidgetComponent, isStandalone: true, selector: "bbq-fileupload-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
640
+ <div
641
+ class="bbq-widget bbq-file-upload"
642
+ [attr.data-widget-type]="'fileupload'">
643
+ <label *ngIf="showLabel" class="bbq-file-label" [attr.for]="inputId">
644
+ {{ fileUploadWidget.label }}
645
+ </label>
646
+ <input
647
+ type="file"
648
+ [id]="inputId"
649
+ [ngClass]="inputClasses"
650
+ [attr.data-action]="fileUploadWidget.action"
651
+ [accept]="fileUploadWidget.accept || ''"
652
+ [attr.data-max-bytes]="fileUploadWidget.maxBytes"
653
+ (change)="onFileChange($event)" />
654
+ </div>
655
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
656
+ }
657
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FileUploadWidgetComponent, decorators: [{
658
+ type: Component,
659
+ args: [{ selector: 'bbq-fileupload-widget', standalone: true, imports: [CommonModule], template: `
660
+ <div
661
+ class="bbq-widget bbq-file-upload"
662
+ [attr.data-widget-type]="'fileupload'">
663
+ <label *ngIf="showLabel" class="bbq-file-label" [attr.for]="inputId">
664
+ {{ fileUploadWidget.label }}
665
+ </label>
666
+ <input
667
+ type="file"
668
+ [id]="inputId"
669
+ [ngClass]="inputClasses"
670
+ [attr.data-action]="fileUploadWidget.action"
671
+ [accept]="fileUploadWidget.accept || ''"
672
+ [attr.data-max-bytes]="fileUploadWidget.maxBytes"
673
+ (change)="onFileChange($event)" />
674
+ </div>
675
+ ` }]
676
+ }], propDecorators: { widget: [{
677
+ type: Input
678
+ }] } });
679
+
680
+ class ThemeSwitcherWidgetComponent {
681
+ widget;
682
+ widgetAction;
683
+ value = '';
684
+ selectId = '';
685
+ get themeSwitcherWidget() {
686
+ return this.widget;
687
+ }
688
+ ngOnInit() {
689
+ this.selectId = `bbq-${this.themeSwitcherWidget.action.replace(/\s+/g, '-').toLowerCase()}-select`;
690
+ this.value = this.themeSwitcherWidget.themes[0] || '';
691
+ }
692
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ThemeSwitcherWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
693
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: ThemeSwitcherWidgetComponent, isStandalone: true, selector: "bbq-themeswitcher-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
694
+ <div
695
+ class="bbq-widget bbq-theme-switcher"
696
+ [attr.data-widget-type]="'themeswitcher'">
697
+ <label class="bbq-theme-switcher-label" [attr.for]="selectId">
698
+ {{ themeSwitcherWidget.label }}
699
+ </label>
700
+ <select
701
+ [id]="selectId"
702
+ class="bbq-theme-switcher-select"
703
+ [attr.data-action]="themeSwitcherWidget.action"
704
+ [(ngModel)]="value">
705
+ @for (theme of themeSwitcherWidget.themes; track theme) {
706
+ <option [value]="theme">{{ theme }}</option>
707
+ }
708
+ </select>
709
+ </div>
710
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
711
+ }
712
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ThemeSwitcherWidgetComponent, decorators: [{
713
+ type: Component,
714
+ args: [{ selector: 'bbq-themeswitcher-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
715
+ <div
716
+ class="bbq-widget bbq-theme-switcher"
717
+ [attr.data-widget-type]="'themeswitcher'">
718
+ <label class="bbq-theme-switcher-label" [attr.for]="selectId">
719
+ {{ themeSwitcherWidget.label }}
720
+ </label>
721
+ <select
722
+ [id]="selectId"
723
+ class="bbq-theme-switcher-select"
724
+ [attr.data-action]="themeSwitcherWidget.action"
725
+ [(ngModel)]="value">
726
+ @for (theme of themeSwitcherWidget.themes; track theme) {
727
+ <option [value]="theme">{{ theme }}</option>
728
+ }
729
+ </select>
730
+ </div>
731
+ ` }]
732
+ }], propDecorators: { widget: [{
733
+ type: Input
734
+ }] } });
735
+
736
+ const typeMap = {
737
+ 'input': 'input',
738
+ 'text': 'input',
739
+ 'email': 'input',
740
+ 'number': 'input',
741
+ 'password': 'input',
742
+ 'textarea': 'textarea',
743
+ 'dropdown': 'dropdown',
744
+ 'select': 'dropdown',
745
+ 'slider': 'slider',
746
+ 'toggle': 'toggle',
747
+ 'datepicker': 'datepicker',
748
+ 'date': 'datepicker',
749
+ 'multiselect': 'multiselect',
750
+ 'fileupload': 'fileupload'
751
+ };
752
+
753
+ class DatePickerWidgetComponent {
754
+ widget;
755
+ widgetAction;
756
+ value = '';
757
+ inputId = '';
758
+ get datePickerWidget() {
759
+ return this.widget;
760
+ }
761
+ get showLabel() {
762
+ const widget = this.datePickerWidget;
763
+ if (widget.hideLabel === true) {
764
+ return false;
765
+ }
766
+ if (widget.showLabel === false) {
767
+ return false;
768
+ }
769
+ return true;
770
+ }
771
+ get inputClasses() {
772
+ return this.isFormAppearance ? ['bbq-form-datepicker'] : ['bbq-form-datepicker', 'bbq-input'];
773
+ }
774
+ get isFormAppearance() {
775
+ return this.datePickerWidget.appearance === 'form';
776
+ }
777
+ ngOnInit() {
778
+ const type = typeMap[this.datePickerWidget.type] || 'date';
779
+ this.inputId = `bbq-${this.datePickerWidget.action.replace(/\s+/g, '-').toLowerCase()}-${type}`;
780
+ this.value = this.datePickerWidget.defaultValue ?? '';
781
+ }
782
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DatePickerWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
783
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: DatePickerWidgetComponent, isStandalone: true, selector: "bbq-datepicker-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
784
+ <div
785
+ class="bbq-widget bbq-date-picker"
786
+ [attr.data-widget-type]="'datepicker'">
787
+ <label *ngIf="showLabel" class="bbq-date-picker-label" [attr.for]="inputId">
788
+ {{ datePickerWidget.label }}
789
+ </label>
790
+ <input
791
+ type="date"
792
+ [id]="inputId"
793
+ [ngClass]="inputClasses"
794
+ [attr.data-action]="datePickerWidget.action"
795
+ [min]="datePickerWidget.minDate || ''"
796
+ [max]="datePickerWidget.maxDate || ''"
797
+ [(ngModel)]="value" />
798
+ </div>
799
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
800
+ }
801
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DatePickerWidgetComponent, decorators: [{
802
+ type: Component,
803
+ args: [{ selector: 'bbq-datepicker-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
804
+ <div
805
+ class="bbq-widget bbq-date-picker"
806
+ [attr.data-widget-type]="'datepicker'">
807
+ <label *ngIf="showLabel" class="bbq-date-picker-label" [attr.for]="inputId">
808
+ {{ datePickerWidget.label }}
809
+ </label>
810
+ <input
811
+ type="date"
812
+ [id]="inputId"
813
+ [ngClass]="inputClasses"
814
+ [attr.data-action]="datePickerWidget.action"
815
+ [min]="datePickerWidget.minDate || ''"
816
+ [max]="datePickerWidget.maxDate || ''"
817
+ [(ngModel)]="value" />
818
+ </div>
819
+ ` }]
820
+ }], propDecorators: { widget: [{
821
+ type: Input
822
+ }] } });
823
+
824
+ class MultiSelectWidgetComponent {
825
+ widget;
826
+ widgetAction;
827
+ values = [];
828
+ selectId = '';
829
+ get multiSelectWidget() {
830
+ return this.widget;
831
+ }
832
+ get showLabel() {
833
+ const widget = this.multiSelectWidget;
834
+ if (widget.hideLabel === true) {
835
+ return false;
836
+ }
837
+ if (widget.showLabel === false) {
838
+ return false;
839
+ }
840
+ return true;
841
+ }
842
+ get selectClasses() {
843
+ return this.isFormAppearance
844
+ ? ['bbq-form-multiselect', 'bbq-form-select']
845
+ : ['bbq-form-multiselect', 'bbq-form-select'];
846
+ }
847
+ get isFormAppearance() {
848
+ return this.multiSelectWidget.appearance === 'form';
849
+ }
850
+ ngOnInit() {
851
+ this.selectId = `bbq-${this.multiSelectWidget.action.replace(/\s+/g, '-').toLowerCase()}-select`;
852
+ if (this.multiSelectWidget.defaultValue) {
853
+ this.values = Array.isArray(this.multiSelectWidget.defaultValue)
854
+ ? this.multiSelectWidget.defaultValue
855
+ : [this.multiSelectWidget.defaultValue];
856
+ }
857
+ }
858
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MultiSelectWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
859
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: MultiSelectWidgetComponent, isStandalone: true, selector: "bbq-multiselect-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
860
+ <div
861
+ class="bbq-widget bbq-multi-select"
862
+ [attr.data-widget-type]="'multiselect'">
863
+ <label *ngIf="showLabel" class="bbq-multi-select-label" [attr.for]="selectId">
864
+ {{ multiSelectWidget.label }}
865
+ </label>
866
+ <select
867
+ [id]="selectId"
868
+ [ngClass]="selectClasses"
869
+ [attr.data-action]="multiSelectWidget.action"
870
+ multiple
871
+ [(ngModel)]="values">
872
+ @for (option of multiSelectWidget.options; track option) {
873
+ <option [value]="option">{{ option }}</option>
874
+ }
875
+ </select>
876
+ </div>
877
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectMultipleControlValueAccessor, selector: "select[multiple][formControlName],select[multiple][formControl],select[multiple][ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
878
+ }
879
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MultiSelectWidgetComponent, decorators: [{
880
+ type: Component,
881
+ args: [{ selector: 'bbq-multiselect-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
882
+ <div
883
+ class="bbq-widget bbq-multi-select"
884
+ [attr.data-widget-type]="'multiselect'">
885
+ <label *ngIf="showLabel" class="bbq-multi-select-label" [attr.for]="selectId">
886
+ {{ multiSelectWidget.label }}
887
+ </label>
888
+ <select
889
+ [id]="selectId"
890
+ [ngClass]="selectClasses"
891
+ [attr.data-action]="multiSelectWidget.action"
892
+ multiple
893
+ [(ngModel)]="values">
894
+ @for (option of multiSelectWidget.options; track option) {
895
+ <option [value]="option">{{ option }}</option>
896
+ }
897
+ </select>
898
+ </div>
899
+ ` }]
900
+ }], propDecorators: { widget: [{
901
+ type: Input
902
+ }] } });
903
+
904
+ class ProgressBarWidgetComponent {
905
+ widget;
906
+ widgetAction;
907
+ progressId = '';
908
+ percentage = 0;
909
+ get progressBarWidget() {
910
+ return this.widget;
911
+ }
912
+ ngOnInit() {
913
+ this.progressId = `bbq-${this.progressBarWidget.action.replace(/\s+/g, '-').toLowerCase()}-progress`;
914
+ const max = this.progressBarWidget.max;
915
+ this.percentage = max > 0 ? Math.floor((this.progressBarWidget.value * 100) / max) : 0;
916
+ }
917
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ProgressBarWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
918
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: ProgressBarWidgetComponent, isStandalone: true, selector: "bbq-progressbar-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
919
+ <div
920
+ class="bbq-widget bbq-progress-bar"
921
+ [attr.data-widget-type]="'progressbar'">
922
+ <label class="bbq-progress-bar-label" [attr.for]="progressId">
923
+ {{ progressBarWidget.label }}
924
+ </label>
925
+ <progress
926
+ [id]="progressId"
927
+ class="bbq-progress-bar-element"
928
+ [value]="progressBarWidget.value"
929
+ [max]="progressBarWidget.max"
930
+ [attr.data-action]="progressBarWidget.action"
931
+ [attr.aria-label]="progressBarWidget.label"
932
+ [attr.aria-valuenow]="progressBarWidget.value"
933
+ [attr.aria-valuemin]="0"
934
+ [attr.aria-valuemax]="progressBarWidget.max">
935
+ {{ percentage }}%
936
+ </progress>
937
+ <span class="bbq-progress-bar-value" aria-live="polite">{{ percentage }}%</span>
938
+ </div>
939
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
940
+ }
941
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ProgressBarWidgetComponent, decorators: [{
942
+ type: Component,
943
+ args: [{ selector: 'bbq-progressbar-widget', standalone: true, imports: [CommonModule], template: `
944
+ <div
945
+ class="bbq-widget bbq-progress-bar"
946
+ [attr.data-widget-type]="'progressbar'">
947
+ <label class="bbq-progress-bar-label" [attr.for]="progressId">
948
+ {{ progressBarWidget.label }}
949
+ </label>
950
+ <progress
951
+ [id]="progressId"
952
+ class="bbq-progress-bar-element"
953
+ [value]="progressBarWidget.value"
954
+ [max]="progressBarWidget.max"
955
+ [attr.data-action]="progressBarWidget.action"
956
+ [attr.aria-label]="progressBarWidget.label"
957
+ [attr.aria-valuenow]="progressBarWidget.value"
958
+ [attr.aria-valuemin]="0"
959
+ [attr.aria-valuemax]="progressBarWidget.max">
960
+ {{ percentage }}%
961
+ </progress>
962
+ <span class="bbq-progress-bar-value" aria-live="polite">{{ percentage }}%</span>
963
+ </div>
964
+ ` }]
965
+ }], propDecorators: { widget: [{
966
+ type: Input
967
+ }] } });
968
+
969
+ class FormValidationService {
970
+ subject = new Subject();
971
+ get validation$() {
972
+ return this.subject.asObservable();
973
+ }
974
+ emit(event) {
975
+ try {
976
+ this.subject.next(event);
977
+ }
978
+ catch { }
979
+ }
980
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FormValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
981
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FormValidationService, providedIn: 'root' });
982
+ }
983
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FormValidationService, decorators: [{
984
+ type: Injectable,
985
+ args: [{ providedIn: 'root' }]
986
+ }] });
987
+
988
+ /**
989
+ * Helper class to wrap form fields as widgets for dynamic rendering
990
+ */
991
+ class FormFieldWidget {
992
+ field;
993
+ formId;
994
+ type;
995
+ label;
996
+ action;
997
+ appearance = 'form';
998
+ hideLabel = true;
999
+ constructor(field, formId) {
1000
+ this.field = field;
1001
+ this.formId = formId;
1002
+ this.type = this.mapFieldTypeToWidgetType(field.type);
1003
+ this.label = field.label;
1004
+ this.action = `${formId}_${field.name}`;
1005
+ }
1006
+ mapFieldTypeToWidgetType(fieldType) {
1007
+ return typeMap[fieldType] || 'input';
1008
+ }
1009
+ // Map field properties to widget properties
1010
+ get placeholder() {
1011
+ return this.field.placeholder ?? undefined;
1012
+ }
1013
+ get maxLength() {
1014
+ return this.field['maxLength'];
1015
+ }
1016
+ get rows() {
1017
+ return this.field['rows'];
1018
+ }
1019
+ get options() {
1020
+ return this.field['options'] || [];
1021
+ }
1022
+ get min() {
1023
+ return this.field['min'] ?? 0;
1024
+ }
1025
+ get max() {
1026
+ return this.field['max'] ?? 100;
1027
+ }
1028
+ get step() {
1029
+ return this.field['step'] ?? 1;
1030
+ }
1031
+ get defaultValue() {
1032
+ return this.field['defaultValue'] ?? (this.type === 'slider' ? this.min : undefined);
1033
+ }
1034
+ get minDate() {
1035
+ return this.field['minDate'];
1036
+ }
1037
+ get maxDate() {
1038
+ return this.field['maxDate'];
1039
+ }
1040
+ get accept() {
1041
+ return this.field['accept'];
1042
+ }
1043
+ get maxBytes() {
1044
+ return this.field['maxBytes'];
1045
+ }
1046
+ // ChatWidget interface methods
1047
+ toJson() {
1048
+ return JSON.stringify(this.toObject());
1049
+ }
1050
+ toObject() {
1051
+ return {
1052
+ type: this.type,
1053
+ label: this.label,
1054
+ action: this.action,
1055
+ ...this.field
1056
+ };
1057
+ }
1058
+ }
1059
+ class FormWidgetComponent {
1060
+ injector;
1061
+ environmentInjector;
1062
+ hostRef;
1063
+ formValidationService;
1064
+ widget;
1065
+ widgetAction;
1066
+ fieldComponentRegistryOverride;
1067
+ validationState = new EventEmitter();
1068
+ fieldContainers;
1069
+ formId = '';
1070
+ formData = {};
1071
+ showValidationMessage = false;
1072
+ componentRefs = [];
1073
+ // Component registry for field types (can be extended via `fieldComponentRegistryOverride`)
1074
+ fieldComponentRegistry = {
1075
+ 'input': InputWidgetComponent,
1076
+ 'text': InputWidgetComponent,
1077
+ 'email': InputWidgetComponent,
1078
+ 'number': InputWidgetComponent,
1079
+ 'password': InputWidgetComponent,
1080
+ 'textarea': TextAreaWidgetComponent,
1081
+ 'dropdown': DropdownWidgetComponent,
1082
+ 'select': DropdownWidgetComponent,
1083
+ 'slider': SliderWidgetComponent,
1084
+ 'toggle': ToggleWidgetComponent,
1085
+ 'datepicker': DatePickerWidgetComponent,
1086
+ 'date': DatePickerWidgetComponent,
1087
+ 'multiselect': MultiSelectWidgetComponent,
1088
+ 'fileupload': FileUploadWidgetComponent,
1089
+ 'checkbox': ToggleWidgetComponent,
1090
+ 'radio': ToggleWidgetComponent,
1091
+ };
1092
+ // Whether the form has been submitted; when true, user interaction is disabled
1093
+ isSubmitted = false;
1094
+ get formWidget() {
1095
+ return this.widget;
1096
+ }
1097
+ constructor(injector, environmentInjector, hostRef, formValidationService) {
1098
+ this.injector = injector;
1099
+ this.environmentInjector = environmentInjector;
1100
+ this.hostRef = hostRef;
1101
+ this.formValidationService = formValidationService;
1102
+ }
1103
+ ngOnInit() {
1104
+ this.formId = `bbq-${this.formWidget.action.replace(/\s+/g, '-').toLowerCase()}`;
1105
+ // Initialize form data with default values
1106
+ for (const field of this.formWidget.fields || []) {
1107
+ if (field.type === 'slider') {
1108
+ this.formData[field.name] = field['default'] ?? field['defaultValue'] ?? field['min'] ?? 0;
1109
+ }
1110
+ else if (field.type === 'toggle' || field.type === 'checkbox') {
1111
+ this.formData[field.name] = field['defaultValue'] ?? false;
1112
+ }
1113
+ else if (field.type === 'multiselect') {
1114
+ this.formData[field.name] = [];
1115
+ }
1116
+ else {
1117
+ this.formData[field.name] = '';
1118
+ }
1119
+ }
1120
+ // Merge any overrides provided by the consumer
1121
+ if (this.fieldComponentRegistryOverride) {
1122
+ this.fieldComponentRegistry = { ...this.fieldComponentRegistry, ...this.fieldComponentRegistryOverride };
1123
+ }
1124
+ }
1125
+ ngAfterViewInit() {
1126
+ // Render field widgets dynamically
1127
+ setTimeout(() => this.renderFieldWidgets(), 0);
1128
+ }
1129
+ ngOnDestroy() {
1130
+ // Clean up component refs
1131
+ this.componentRefs.forEach(ref => ref.destroy());
1132
+ this.componentRefs = [];
1133
+ }
1134
+ renderFieldWidgets() {
1135
+ const containers = this.fieldContainers.toArray();
1136
+ const fields = this.formWidget.fields || [];
1137
+ fields.forEach((field, index) => {
1138
+ const container = containers[index];
1139
+ if (!container)
1140
+ return;
1141
+ const componentType = this.fieldComponentRegistry[field.type];
1142
+ if (!componentType) {
1143
+ // Fallback to input for unknown types
1144
+ this.renderInputFallback(container, field);
1145
+ return;
1146
+ }
1147
+ // Create the field widget
1148
+ const fieldWidget = new FormFieldWidget(field, this.formId);
1149
+ // Create the component
1150
+ const componentRef = createComponent(componentType, {
1151
+ environmentInjector: this.environmentInjector,
1152
+ elementInjector: this.injector,
1153
+ });
1154
+ // Set component inputs
1155
+ const instance = componentRef.instance;
1156
+ instance['widget'] = fieldWidget;
1157
+ // Pass current disabled state so custom components can opt-in to being readonly
1158
+ instance['disabled'] = this.isSubmitted;
1159
+ // Connect to form data via widgetAction
1160
+ instance['widgetAction'] = (actionName, payload) => {
1161
+ // Handle field value changes - for now, we'll sync via the rendered widget's internal state
1162
+ // The actual form submission will gather values from the DOM
1163
+ };
1164
+ // Attach to container
1165
+ container.insert(componentRef.hostView);
1166
+ this.componentRefs.push(componentRef);
1167
+ // Trigger change detection
1168
+ componentRef.changeDetectorRef.detectChanges();
1169
+ });
1170
+ }
1171
+ renderInputFallback(container, field) {
1172
+ // For unsupported field types, render a basic input
1173
+ const fieldWidget = new FormFieldWidget(field, this.formId);
1174
+ const componentRef = createComponent(InputWidgetComponent, {
1175
+ environmentInjector: this.environmentInjector,
1176
+ elementInjector: this.injector,
1177
+ });
1178
+ const instance = componentRef.instance;
1179
+ instance['widget'] = fieldWidget;
1180
+ instance['disabled'] = this.isSubmitted;
1181
+ container.insert(componentRef.hostView);
1182
+ this.componentRefs.push(componentRef);
1183
+ componentRef.changeDetectorRef.detectChanges();
1184
+ }
1185
+ getFieldId(field) {
1186
+ // Match the ID format used by dynamically rendered input widgets
1187
+ return `bbq-${this.formId}_${field.name.toLowerCase()}-${typeMap[field.type] || 'input'}`;
1188
+ }
1189
+ getFieldProp(field, prop) {
1190
+ return field[prop];
1191
+ }
1192
+ onActionClick(actionType) {
1193
+ if (this.isSubmitted)
1194
+ return;
1195
+ if (actionType === 'submit') {
1196
+ // Gather form data from the DOM BEFORE validation (since widgets manage their own state)
1197
+ this.gatherFormData();
1198
+ // Validate required fields
1199
+ const hasErrors = this.validateForm();
1200
+ if (hasErrors) {
1201
+ this.showValidationMessage = true;
1202
+ return;
1203
+ }
1204
+ this.showValidationMessage = false;
1205
+ // Mark submitted to prevent further interaction
1206
+ this.isSubmitted = true;
1207
+ // Inform child components and disable DOM controls
1208
+ this.componentRefs.forEach(ref => {
1209
+ try {
1210
+ ref.instance['disabled'] = true;
1211
+ ref.changeDetectorRef.detectChanges();
1212
+ }
1213
+ catch { }
1214
+ });
1215
+ this.disableFormInteraction();
1216
+ if (this.widgetAction) {
1217
+ this.widgetAction(this.formWidget.action, this.formData);
1218
+ }
1219
+ }
1220
+ else {
1221
+ // Cancel or other actions
1222
+ if (this.widgetAction) {
1223
+ this.widgetAction(this.formWidget.action, { actionType });
1224
+ }
1225
+ }
1226
+ }
1227
+ validateForm() {
1228
+ const errors = [];
1229
+ for (const field of this.formWidget.fields || []) {
1230
+ if (field.required) {
1231
+ const value = this.formData[field.name];
1232
+ if (value === undefined || value === null || value === '' ||
1233
+ (Array.isArray(value) && value.length === 0)) {
1234
+ errors.push({ field: field.name, reason: 'required' });
1235
+ }
1236
+ }
1237
+ }
1238
+ const hasErrors = errors.length > 0;
1239
+ const payload = { formId: this.formId, valid: !hasErrors, errors };
1240
+ // Emit to the local Output for direct consumers
1241
+ this.validationState.emit({ valid: !hasErrors, errors });
1242
+ // Also publish via the shared service so consumers that don't have direct access
1243
+ // to the component instance can subscribe app-wide.
1244
+ try {
1245
+ this.formValidationService.emit(payload);
1246
+ }
1247
+ catch { }
1248
+ return hasErrors; // true when there are errors
1249
+ }
1250
+ disableFormInteraction() {
1251
+ try {
1252
+ const root = this.hostRef?.nativeElement;
1253
+ if (!root)
1254
+ return;
1255
+ const controls = root.querySelectorAll('input,select,textarea,button');
1256
+ controls.forEach((el) => {
1257
+ try {
1258
+ el.disabled = true;
1259
+ }
1260
+ catch { }
1261
+ });
1262
+ root.setAttribute('aria-disabled', 'true');
1263
+ }
1264
+ catch { }
1265
+ }
1266
+ gatherFormData() {
1267
+ // Gather data from the rendered field widgets
1268
+ // Since each widget component manages its own state via ngModel,
1269
+ // we need to query the DOM to get the current values
1270
+ const fields = this.formWidget.fields || [];
1271
+ fields.forEach((field) => {
1272
+ const fieldId = this.getFieldId(field);
1273
+ const element = document.getElementById(fieldId);
1274
+ if (element) {
1275
+ if (element.type === 'checkbox') {
1276
+ this.formData[field.name] = element.checked;
1277
+ }
1278
+ else if (element.type === 'file') {
1279
+ this.formData[field.name] = element.files?.[0];
1280
+ }
1281
+ else if (element.tagName === 'SELECT' && element.multiple) {
1282
+ const select = element;
1283
+ this.formData[field.name] = Array.from(select.selectedOptions).map(opt => opt.value);
1284
+ }
1285
+ else {
1286
+ this.formData[field.name] = element.value;
1287
+ }
1288
+ }
1289
+ });
1290
+ }
1291
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FormWidgetComponent, deps: [{ token: i0.Injector }, { token: i0.EnvironmentInjector }, { token: i0.ElementRef }, { token: FormValidationService }], target: i0.ɵɵFactoryTarget.Component });
1292
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: FormWidgetComponent, isStandalone: true, selector: "bbq-form-widget", inputs: { widget: "widget", fieldComponentRegistryOverride: "fieldComponentRegistryOverride" }, outputs: { validationState: "validationState" }, viewQueries: [{ propertyName: "fieldContainers", predicate: ["fieldContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: `
1293
+ <div
1294
+ class="bbq-widget bbq-form"
1295
+ [class.bbq-form-submitted]="isSubmitted"
1296
+ [attr.aria-disabled]="isSubmitted ? 'true' : null"
1297
+ [attr.data-widget-id]="formId"
1298
+ [attr.data-widget-type]="'form'"
1299
+ [attr.data-action]="formWidget.action">
1300
+ <fieldset class="bbq-form-fieldset">
1301
+ <legend class="bbq-form-title">{{ formWidget.title }}</legend>
1302
+
1303
+ @for (field of formWidget.fields; track field.name) {
1304
+ <div
1305
+ class="bbq-form-field"
1306
+ [class.bbq-form-field-required]="field.required"
1307
+ [attr.data-required]="field.required ? 'true' : null">
1308
+ <label class="bbq-form-field-label" [attr.for]="getFieldId(field)">
1309
+ {{ field.label }}
1310
+ @if (field.required) {
1311
+ <span class="bbq-form-required">*</span>
1312
+ }
1313
+ </label>
1314
+
1315
+ <div #fieldContainer class="bbq-form-field-widget"></div>
1316
+
1317
+ @if (getFieldProp(field, 'validationHint')) {
1318
+ <span class="bbq-form-field-hint">{{ getFieldProp(field, 'validationHint') }}</span>
1319
+ }
1320
+ </div>
1321
+ }
1322
+
1323
+ <div class="bbq-form-validation-message" [style.display]="showValidationMessage ? 'block' : 'none'">
1324
+ Please fill in all required fields before submitting.
1325
+ </div>
1326
+
1327
+ @if (formWidget.actions && formWidget.actions.length > 0) {
1328
+ <div class="bbq-form-actions">
1329
+ @for (action of formWidget.actions; track action.label) {
1330
+ <button
1331
+ type="button"
1332
+ class="bbq-form-button"
1333
+ [class.bbq-form-submit]="action.type === 'submit'"
1334
+ [class.bbq-form-cancel]="action.type !== 'submit'"
1335
+ [attr.data-action]="formWidget.action"
1336
+ [attr.data-action-type]="action.type"
1337
+ [disabled]="isSubmitted"
1338
+ (click)="onActionClick(action.type)">
1339
+ {{ action.label }}
1340
+ </button>
1341
+ }
1342
+ </div>
1343
+ }
1344
+ </fieldset>
1345
+ </div>
1346
+ `, isInline: true, styles: [".bbq-form-field-widget{display:contents}.bbq-form-submitted{opacity:.7}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
1347
+ }
1348
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FormWidgetComponent, decorators: [{
1349
+ type: Component,
1350
+ args: [{ selector: 'bbq-form-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
1351
+ <div
1352
+ class="bbq-widget bbq-form"
1353
+ [class.bbq-form-submitted]="isSubmitted"
1354
+ [attr.aria-disabled]="isSubmitted ? 'true' : null"
1355
+ [attr.data-widget-id]="formId"
1356
+ [attr.data-widget-type]="'form'"
1357
+ [attr.data-action]="formWidget.action">
1358
+ <fieldset class="bbq-form-fieldset">
1359
+ <legend class="bbq-form-title">{{ formWidget.title }}</legend>
1360
+
1361
+ @for (field of formWidget.fields; track field.name) {
1362
+ <div
1363
+ class="bbq-form-field"
1364
+ [class.bbq-form-field-required]="field.required"
1365
+ [attr.data-required]="field.required ? 'true' : null">
1366
+ <label class="bbq-form-field-label" [attr.for]="getFieldId(field)">
1367
+ {{ field.label }}
1368
+ @if (field.required) {
1369
+ <span class="bbq-form-required">*</span>
1370
+ }
1371
+ </label>
1372
+
1373
+ <div #fieldContainer class="bbq-form-field-widget"></div>
1374
+
1375
+ @if (getFieldProp(field, 'validationHint')) {
1376
+ <span class="bbq-form-field-hint">{{ getFieldProp(field, 'validationHint') }}</span>
1377
+ }
1378
+ </div>
1379
+ }
1380
+
1381
+ <div class="bbq-form-validation-message" [style.display]="showValidationMessage ? 'block' : 'none'">
1382
+ Please fill in all required fields before submitting.
1383
+ </div>
1384
+
1385
+ @if (formWidget.actions && formWidget.actions.length > 0) {
1386
+ <div class="bbq-form-actions">
1387
+ @for (action of formWidget.actions; track action.label) {
1388
+ <button
1389
+ type="button"
1390
+ class="bbq-form-button"
1391
+ [class.bbq-form-submit]="action.type === 'submit'"
1392
+ [class.bbq-form-cancel]="action.type !== 'submit'"
1393
+ [attr.data-action]="formWidget.action"
1394
+ [attr.data-action-type]="action.type"
1395
+ [disabled]="isSubmitted"
1396
+ (click)="onActionClick(action.type)">
1397
+ {{ action.label }}
1398
+ </button>
1399
+ }
1400
+ </div>
1401
+ }
1402
+ </fieldset>
1403
+ </div>
1404
+ `, styles: [".bbq-form-field-widget{display:contents}.bbq-form-submitted{opacity:.7}\n"] }]
1405
+ }], ctorParameters: () => [{ type: i0.Injector }, { type: i0.EnvironmentInjector }, { type: i0.ElementRef }, { type: FormValidationService }], propDecorators: { widget: [{
1406
+ type: Input
1407
+ }], fieldComponentRegistryOverride: [{
1408
+ type: Input
1409
+ }], validationState: [{
1410
+ type: Output
1411
+ }], fieldContainers: [{
1412
+ type: ViewChildren,
1413
+ args: ['fieldContainer', { read: ViewContainerRef }]
1414
+ }] } });
1415
+
1416
+ class ImageWidgetComponent {
1417
+ widget;
1418
+ widgetAction;
1419
+ get imageWidget() {
1420
+ return this.widget;
1421
+ }
1422
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ImageWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1423
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.8", type: ImageWidgetComponent, isStandalone: true, selector: "bbq-image-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
1424
+ <div
1425
+ class="bbq-widget bbq-image"
1426
+ [attr.data-widget-type]="'image'"
1427
+ [attr.data-action]="imageWidget.action">
1428
+ <img
1429
+ class="bbq-image-img"
1430
+ [src]="imageWidget.imageUrl"
1431
+ [alt]="imageWidget.alt || 'Image'"
1432
+ [style.width]="imageWidget.width ? imageWidget.width + 'px' : 'auto'"
1433
+ [style.height]="imageWidget.height ? imageWidget.height + 'px' : 'auto'"
1434
+ loading="lazy" />
1435
+ </div>
1436
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1437
+ }
1438
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ImageWidgetComponent, decorators: [{
1439
+ type: Component,
1440
+ args: [{ selector: 'bbq-image-widget', standalone: true, imports: [CommonModule], template: `
1441
+ <div
1442
+ class="bbq-widget bbq-image"
1443
+ [attr.data-widget-type]="'image'"
1444
+ [attr.data-action]="imageWidget.action">
1445
+ <img
1446
+ class="bbq-image-img"
1447
+ [src]="imageWidget.imageUrl"
1448
+ [alt]="imageWidget.alt || 'Image'"
1449
+ [style.width]="imageWidget.width ? imageWidget.width + 'px' : 'auto'"
1450
+ [style.height]="imageWidget.height ? imageWidget.height + 'px' : 'auto'"
1451
+ loading="lazy" />
1452
+ </div>
1453
+ ` }]
1454
+ }], propDecorators: { widget: [{
1455
+ type: Input
1456
+ }] } });
1457
+
1458
+ class ImageCollectionWidgetComponent {
1459
+ widget;
1460
+ widgetAction;
1461
+ get imageCollectionWidget() {
1462
+ return this.widget;
1463
+ }
1464
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ImageCollectionWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1465
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: ImageCollectionWidgetComponent, isStandalone: true, selector: "bbq-imagecollection-widget", inputs: { widget: "widget" }, ngImport: i0, template: `
1466
+ <div
1467
+ class="bbq-widget bbq-image-collection"
1468
+ [attr.data-widget-type]="'imagecollection'"
1469
+ [attr.data-action]="imageCollectionWidget.action">
1470
+ <div class="bbq-image-collection-grid">
1471
+ @for (image of imageCollectionWidget.images; track image.imageUrl) {
1472
+ <div class="bbq-image-collection-item">
1473
+ <img
1474
+ class="bbq-image-collection-img"
1475
+ [src]="image.imageUrl"
1476
+ [alt]="image.alt || 'Image'"
1477
+ [style.width]="image.width ? image.width + 'px' : 'auto'"
1478
+ [style.height]="image.height ? image.height + 'px' : 'auto'"
1479
+ loading="lazy" />
1480
+ </div>
1481
+ }
1482
+ </div>
1483
+ </div>
1484
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1485
+ }
1486
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ImageCollectionWidgetComponent, decorators: [{
1487
+ type: Component,
1488
+ args: [{ selector: 'bbq-imagecollection-widget', standalone: true, imports: [CommonModule], template: `
1489
+ <div
1490
+ class="bbq-widget bbq-image-collection"
1491
+ [attr.data-widget-type]="'imagecollection'"
1492
+ [attr.data-action]="imageCollectionWidget.action">
1493
+ <div class="bbq-image-collection-grid">
1494
+ @for (image of imageCollectionWidget.images; track image.imageUrl) {
1495
+ <div class="bbq-image-collection-item">
1496
+ <img
1497
+ class="bbq-image-collection-img"
1498
+ [src]="image.imageUrl"
1499
+ [alt]="image.alt || 'Image'"
1500
+ [style.width]="image.width ? image.width + 'px' : 'auto'"
1501
+ [style.height]="image.height ? image.height + 'px' : 'auto'"
1502
+ loading="lazy" />
1503
+ </div>
1504
+ }
1505
+ </div>
1506
+ </div>
1507
+ ` }]
1508
+ }], propDecorators: { widget: [{
1509
+ type: Input
1510
+ }] } });
1511
+
1512
+ /**
1513
+ * Built-in widget components
1514
+ * These components provide Angular implementations for all standard widget types
1515
+ */
1516
+
1517
+ /**
1518
+ * Registry of all built-in widget components
1519
+ * Maps widget type to Angular component class
1520
+ */
1521
+ const BUILT_IN_WIDGET_COMPONENTS = {
1522
+ button: ButtonWidgetComponent,
1523
+ card: CardWidgetComponent,
1524
+ input: InputWidgetComponent,
1525
+ textarea: TextAreaWidgetComponent,
1526
+ dropdown: DropdownWidgetComponent,
1527
+ slider: SliderWidgetComponent,
1528
+ toggle: ToggleWidgetComponent,
1529
+ fileupload: FileUploadWidgetComponent,
1530
+ themeswitcher: ThemeSwitcherWidgetComponent,
1531
+ datepicker: DatePickerWidgetComponent,
1532
+ multiselect: MultiSelectWidgetComponent,
1533
+ progressbar: ProgressBarWidgetComponent,
1534
+ form: FormWidgetComponent,
1535
+ image: ImageWidgetComponent,
1536
+ imagecollection: ImageCollectionWidgetComponent,
1537
+ };
1538
+
1539
+ const WIDGET_EVENT_MANAGER_FACTORY = new InjectionToken('WIDGET_EVENT_MANAGER_FACTORY');
1540
+ /**
1541
+ * Injection token for SsrWidgetRenderer
1542
+ *
1543
+ * Use this token to inject a SsrWidgetRenderer instance in your components.
1544
+ * By default, WidgetRendererComponent provides this token with a factory that creates
1545
+ * a new instance for each component.
1546
+ *
1547
+ * @example
1548
+ * ```typescript
1549
+ * constructor(@Inject(SSR_WIDGET_RENDERER) private renderer: SsrWidgetRenderer) {}
1550
+ * ```
1551
+ */
1552
+ const SSR_WIDGET_RENDERER = new InjectionToken('SSR_WIDGET_RENDERER');
1553
+ /**
1554
+ * Injection token for AngularWidgetRenderer
1555
+ *
1556
+ * Use this token to inject an AngularWidgetRenderer instance in your components.
1557
+ * This is the recommended renderer for Angular applications as it provides
1558
+ * native Angular component rendering instead of HTML string rendering.
1559
+ *
1560
+ * @example
1561
+ * ```typescript
1562
+ * constructor(@Inject(ANGULAR_WIDGET_RENDERER) private renderer: AngularWidgetRenderer) {}
1563
+ * ```
1564
+ */
1565
+ const ANGULAR_WIDGET_RENDERER = new InjectionToken('ANGULAR_WIDGET_RENDERER');
1566
+ /**
1567
+ * Factory function for creating WidgetEventManager instances
1568
+ *
1569
+ * This factory is used by default in WidgetRendererComponent's providers array.
1570
+ * You can override this in your own providers if you need custom initialization.
1571
+ *
1572
+ * @returns A factory function that creates WidgetEventManager instances
1573
+ */
1574
+ function widgetEventManagerFactoryProvider() {
1575
+ return (actionHandler) => new WidgetEventManager(actionHandler);
1576
+ }
1577
+ /**
1578
+ * Factory function for creating SsrWidgetRenderer instances
1579
+ *
1580
+ * This factory is used by default in WidgetRendererComponent's providers array.
1581
+ * You can override this in your own providers if you need custom initialization
1582
+ * or custom rendering options.
1583
+ *
1584
+ * @returns A new SsrWidgetRenderer instance
1585
+ */
1586
+ function ssrWidgetRendererFactory() {
1587
+ return new SsrWidgetRenderer();
1588
+ }
1589
+ /**
1590
+ * Factory function for creating AngularWidgetRenderer instances
1591
+ *
1592
+ * This factory creates an AngularWidgetRenderer with all built-in widget components
1593
+ * pre-registered. This is the recommended renderer for Angular applications.
1594
+ *
1595
+ * @returns A new AngularWidgetRenderer instance with built-in components registered
1596
+ */
1597
+ function angularWidgetRendererFactory() {
1598
+ const renderer = new AngularWidgetRenderer();
1599
+ renderer.registerBuiltInComponents(BUILT_IN_WIDGET_COMPONENTS);
1600
+ return renderer;
1601
+ }
1602
+
1603
+ /**
1604
+ * Service for registering custom widget factories and renderers
1605
+ *
1606
+ * This service provides a centralized way to register custom widget types
1607
+ * that extend the base widget functionality, including support for
1608
+ * Angular components and templates as custom renderers.
1609
+ *
1610
+ * @example
1611
+ * ```typescript
1612
+ * constructor(private widgetRegistry: WidgetRegistryService) {
1613
+ * // Register a widget factory
1614
+ * this.widgetRegistry.registerFactory('myWidget', (obj) => {
1615
+ * if (obj.type === 'myWidget') {
1616
+ * return new MyCustomWidget(obj.label, obj.action);
1617
+ * }
1618
+ * return null;
1619
+ * });
1620
+ *
1621
+ * // Register a component-based renderer
1622
+ * this.widgetRegistry.registerRenderer('myWidget', MyWidgetComponent);
1623
+ * }
1624
+ * ```
1625
+ */
1626
+ class WidgetRegistryService {
1627
+ customRenderers = new Map();
1628
+ /**
1629
+ * Register a custom widget factory function
1630
+ *
1631
+ * @param type - The widget type identifier
1632
+ * @param factory - Factory function that creates widget instances from plain objects
1633
+ */
1634
+ registerFactory(type, factory) {
1635
+ customWidgetRegistry.registerFactory(type, factory);
1636
+ }
1637
+ /**
1638
+ * Register a widget class with automatic factory creation
1639
+ *
1640
+ * @param type - The widget type identifier
1641
+ * @param ctor - Widget class constructor
1642
+ */
1643
+ registerClass(type, ctor) {
1644
+ customWidgetRegistry.registerClass(type, ctor);
1645
+ }
1646
+ /**
1647
+ * Get a factory for a specific widget type
1648
+ *
1649
+ * @param type - The widget type identifier
1650
+ * @returns The factory function if registered, undefined otherwise
1651
+ */
1652
+ getFactory(type) {
1653
+ return customWidgetRegistry.getFactory(type);
1654
+ }
1655
+ /**
1656
+ * Register a custom renderer for a specific widget type
1657
+ *
1658
+ * The renderer can be:
1659
+ * - A function that returns HTML string
1660
+ * - An Angular Component class
1661
+ * - An Angular TemplateRef
1662
+ *
1663
+ * @param type - The widget type identifier
1664
+ * @param renderer - The custom renderer (function, Component, or TemplateRef)
1665
+ *
1666
+ * @example
1667
+ * ```typescript
1668
+ * // HTML function renderer
1669
+ * widgetRegistry.registerRenderer('weather', (widget) => `<div>${widget.label}</div>`);
1670
+ *
1671
+ * // Component renderer
1672
+ * widgetRegistry.registerRenderer('weather', WeatherWidgetComponent);
1673
+ *
1674
+ * // Template renderer (from @ViewChild or elsewhere)
1675
+ * widgetRegistry.registerRenderer('weather', this.weatherTemplate);
1676
+ * ```
1677
+ */
1678
+ registerRenderer(type, renderer) {
1679
+ if (!type || typeof type !== 'string') {
1680
+ throw new Error('type must be a non-empty string');
1681
+ }
1682
+ if (!renderer) {
1683
+ throw new Error('renderer is required');
1684
+ }
1685
+ this.customRenderers.set(type, renderer);
1686
+ }
1687
+ /**
1688
+ * Get a custom renderer for a specific widget type
1689
+ *
1690
+ * @param type - The widget type identifier
1691
+ * @returns The custom renderer if registered, undefined otherwise
1692
+ */
1693
+ getRenderer(type) {
1694
+ return this.customRenderers.get(type);
1695
+ }
1696
+ /**
1697
+ * Check if a custom renderer is registered for a widget type
1698
+ *
1699
+ * @param type - The widget type identifier
1700
+ * @returns True if a custom renderer is registered, false otherwise
1701
+ */
1702
+ hasRenderer(type) {
1703
+ return this.customRenderers.has(type);
1704
+ }
1705
+ /**
1706
+ * Unregister a custom renderer for a widget type
1707
+ *
1708
+ * @param type - The widget type identifier
1709
+ * @returns True if a renderer was removed, false if none was registered
1710
+ */
1711
+ unregisterRenderer(type) {
1712
+ return this.customRenderers.delete(type);
1713
+ }
1714
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: WidgetRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1715
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: WidgetRegistryService, providedIn: 'root' });
1716
+ }
1717
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: WidgetRegistryService, decorators: [{
1718
+ type: Injectable,
1719
+ args: [{
1720
+ providedIn: 'root',
1721
+ }]
1722
+ }] });
1723
+
1724
+ /**
1725
+ * Angular component for rendering chat widgets
1726
+ *
1727
+ * This component handles rendering of chat widgets using the BbQ ChatWidgets library.
1728
+ * It manages widget lifecycle, event handling, and cleanup.
1729
+ *
1730
+ * Supports three types of custom widget renderers:
1731
+ * 1. HTML function renderers (return HTML strings)
1732
+ * 2. Angular Component renderers (render as dynamic components)
1733
+ * 3. Angular TemplateRef renderers (render as embedded views)
1734
+ *
1735
+ * @example
1736
+ * ```typescript
1737
+ * <bbq-widget-renderer
1738
+ * [widgets]="messageWidgets"
1739
+ * (widgetAction)="handleWidgetAction($event)">
1740
+ * </bbq-widget-renderer>
1741
+ * ```
1742
+ */
1743
+ class WidgetRendererComponent {
1744
+ renderer;
1745
+ angularRenderer;
1746
+ eventManagerFactory;
1747
+ widgetRegistry;
1748
+ injector;
1749
+ environmentInjector;
1750
+ /**
1751
+ * Array of widgets to render
1752
+ */
1753
+ widgets;
1754
+ /**
1755
+ * Emits when a widget action is triggered
1756
+ */
1757
+ widgetAction = new EventEmitter();
1758
+ containerRef;
1759
+ widgetItems = [];
1760
+ eventManager;
1761
+ isViewInitialized = false;
1762
+ dynamicComponents = [];
1763
+ dynamicViews = [];
1764
+ constructor(renderer, angularRenderer, eventManagerFactory, widgetRegistry, injector, environmentInjector) {
1765
+ this.renderer = renderer;
1766
+ this.angularRenderer = angularRenderer;
1767
+ this.eventManagerFactory = eventManagerFactory;
1768
+ this.widgetRegistry = widgetRegistry;
1769
+ this.injector = injector;
1770
+ this.environmentInjector = environmentInjector;
1771
+ }
1772
+ ngOnInit() {
1773
+ // this.updateWidgetHtml();
1774
+ }
1775
+ ngOnChanges(changes) {
1776
+ if (changes['widgets']) {
1777
+ this.updateWidgetHtml();
1778
+ }
1779
+ }
1780
+ ngAfterViewInit() {
1781
+ this.updateWidgetHtml();
1782
+ this.isViewInitialized = true;
1783
+ this.setupEventHandlers();
1784
+ // Render dynamic components/templates after view init
1785
+ this.renderDynamicWidgets();
1786
+ }
1787
+ ngOnDestroy() {
1788
+ this.cleanup();
1789
+ }
1790
+ /**
1791
+ * Base implementation for updating the rendered HTML for the current widgets.
1792
+ *
1793
+ * Subclasses may override this method to customize how widgets are rendered
1794
+ * (for example, to inject additional markup or perform preprocessing).
1795
+ *
1796
+ * Since this is the base implementation, overriding implementations are not
1797
+ * required to call `super.updateWidgetHtml()`.
1798
+ */
1799
+ updateWidgetHtml() {
1800
+ if (!this.widgets || this.widgets.length === 0) {
1801
+ this.widgetItems = [];
1802
+ return;
1803
+ }
1804
+ this.widgetItems = this.widgets.map((widget, index) => {
1805
+ const customRenderer = this.widgetRegistry.getRenderer(widget.type);
1806
+ // Check template renderer first (most specific)
1807
+ if (customRenderer && isTemplateRenderer(customRenderer)) {
1808
+ return {
1809
+ index,
1810
+ widget,
1811
+ isHtml: false,
1812
+ };
1813
+ }
1814
+ // Check component renderer second
1815
+ if (customRenderer && isComponentRenderer(customRenderer)) {
1816
+ return {
1817
+ index,
1818
+ widget,
1819
+ isHtml: false,
1820
+ };
1821
+ }
1822
+ // Check HTML function renderer last (most general, matches any function)
1823
+ if (customRenderer && isHtmlRenderer(customRenderer)) {
1824
+ return {
1825
+ index,
1826
+ widget,
1827
+ isHtml: true,
1828
+ html: customRenderer(widget),
1829
+ };
1830
+ }
1831
+ // Try to use AngularWidgetRenderer for built-in widgets
1832
+ if (this.angularRenderer) {
1833
+ const componentType = this.angularRenderer.getComponentType(widget);
1834
+ if (componentType) {
1835
+ return {
1836
+ index,
1837
+ widget,
1838
+ isHtml: false,
1839
+ };
1840
+ }
1841
+ }
1842
+ // Fallback: render using the SSR library renderer
1843
+ return {
1844
+ index,
1845
+ widget,
1846
+ isHtml: true,
1847
+ html: this.renderer.renderWidget(widget),
1848
+ };
1849
+ });
1850
+ // After view updates, reinitialize widgets only if view is already initialized
1851
+ if (this.isViewInitialized) {
1852
+ setTimeout(() => {
1853
+ this.setupEventHandlers();
1854
+ this.renderDynamicWidgets();
1855
+ }, 0);
1856
+ }
1857
+ }
1858
+ /**
1859
+ * Render dynamic components and templates for custom widgets
1860
+ */
1861
+ renderDynamicWidgets() {
1862
+ if (!this.containerRef?.nativeElement)
1863
+ return;
1864
+ // Use microtask to ensure Angular has completed change detection
1865
+ Promise.resolve().then(() => {
1866
+ if (!this.containerRef?.nativeElement)
1867
+ return;
1868
+ // Clean up existing dynamic components and views
1869
+ this.cleanupDynamicWidgets();
1870
+ const container = this.containerRef.nativeElement;
1871
+ // Query all widget divs without the data-rendered filter
1872
+ const dynamicWidgetDivs = Array.from(container.querySelectorAll('.bbq-widget'));
1873
+ let dynamicIndex = 0;
1874
+ this.widgetItems.forEach((item) => {
1875
+ if (!item.isHtml) {
1876
+ const customRenderer = this.widgetRegistry.getRenderer(item.widget.type);
1877
+ const targetDiv = dynamicWidgetDivs[dynamicIndex];
1878
+ if (!targetDiv)
1879
+ return;
1880
+ // Clear the div content before rendering
1881
+ targetDiv.innerHTML = '';
1882
+ // Handle custom renderers first
1883
+ if (customRenderer) {
1884
+ if (isComponentRenderer(customRenderer)) {
1885
+ this.renderComponent(customRenderer, item.widget, targetDiv);
1886
+ }
1887
+ else if (isTemplateRenderer(customRenderer)) {
1888
+ this.renderTemplate(customRenderer, item.widget, targetDiv);
1889
+ }
1890
+ }
1891
+ else if (this.angularRenderer) {
1892
+ // Try to render using AngularWidgetRenderer for built-in widgets
1893
+ const componentType = this.angularRenderer.getComponentType(item.widget);
1894
+ if (componentType) {
1895
+ this.renderComponent(componentType, item.widget, targetDiv);
1896
+ }
1897
+ }
1898
+ dynamicIndex++;
1899
+ }
1900
+ });
1901
+ });
1902
+ }
1903
+ /**
1904
+ * Render an Angular component for a custom widget
1905
+ *
1906
+ * Note: This method safely assigns properties to component instances
1907
+ * by checking for property existence at runtime. This approach is necessary
1908
+ * because we cannot statically verify that all components implement
1909
+ * the CustomWidgetComponent interface.
1910
+ */
1911
+ renderComponent(componentType, widget, targetElement) {
1912
+ // Create the component using Angular's createComponent API
1913
+ const componentRef = createComponent(componentType, {
1914
+ environmentInjector: this.environmentInjector,
1915
+ elementInjector: this.injector,
1916
+ });
1917
+ // Safely set component inputs if they exist
1918
+ const instance = componentRef.instance;
1919
+ // Set widget property if it exists in the prototype chain
1920
+ if (!instance['widget']) {
1921
+ instance['widget'] = widget;
1922
+ }
1923
+ // Set widgetAction property if it exists in the prototype chain
1924
+ if (!instance['widgetAction']) {
1925
+ instance['widgetAction'] = (actionName, payload) => {
1926
+ this.widgetAction.emit({ actionName, payload });
1927
+ };
1928
+ }
1929
+ // Attach the component's host view to the target element
1930
+ targetElement.appendChild(componentRef.location.nativeElement);
1931
+ // Store reference for cleanup
1932
+ this.dynamicComponents.push(componentRef);
1933
+ // Trigger change detection (use optional chaining for safety)
1934
+ componentRef.changeDetectorRef?.detectChanges();
1935
+ }
1936
+ /**
1937
+ * Render an Angular template for a custom widget
1938
+ */
1939
+ renderTemplate(templateRef, widget, targetElement) {
1940
+ const context = {
1941
+ $implicit: widget,
1942
+ widget: widget,
1943
+ emitAction: (actionName, payload) => {
1944
+ this.widgetAction.emit({ actionName, payload });
1945
+ },
1946
+ };
1947
+ const viewRef = templateRef.createEmbeddedView(context);
1948
+ // Attach the view's DOM nodes to the target element
1949
+ viewRef.rootNodes.forEach((node) => {
1950
+ targetElement.appendChild(node);
1951
+ });
1952
+ // Store reference for cleanup
1953
+ this.dynamicViews.push(viewRef);
1954
+ // Trigger change detection
1955
+ viewRef.detectChanges();
1956
+ }
1957
+ /**
1958
+ * Cleanup dynamic components and views
1959
+ */
1960
+ cleanupDynamicWidgets() {
1961
+ this.dynamicComponents.forEach((componentRef) => {
1962
+ componentRef.destroy();
1963
+ });
1964
+ this.dynamicComponents = [];
1965
+ this.dynamicViews.forEach((viewRef) => {
1966
+ viewRef.destroy();
1967
+ });
1968
+ this.dynamicViews = [];
1969
+ }
1970
+ setupEventHandlers() {
1971
+ if (!this.containerRef?.nativeElement)
1972
+ return;
1973
+ // Cleanup old resources before setting up new ones
1974
+ this.cleanup();
1975
+ const container = this.containerRef.nativeElement;
1976
+ // Create a custom action handler that emits events
1977
+ const actionHandler = {
1978
+ handle: async (action, payload) => {
1979
+ this.widgetAction.emit({ actionName: action, payload });
1980
+ },
1981
+ };
1982
+ // Use the injected factory to create an event manager with the component-specific action handler
1983
+ this.eventManager = this.eventManagerFactory(actionHandler);
1984
+ this.eventManager.attachHandlers(container);
1985
+ }
1986
+ handleClick(event) {
1987
+ const target = event.target;
1988
+ // Only trigger actions on non-form buttons and clickable elements (cards)
1989
+ // Don't trigger on input elements or form buttons (let WidgetEventManager handle those)
1990
+ const button = target.tagName === 'BUTTON' ? target : target.closest('button');
1991
+ if (button && !button.closest('[data-widget-type="form"]')) {
1992
+ const actionName = button.getAttribute('data-action');
1993
+ if (actionName) {
1994
+ try {
1995
+ const payloadStr = button.getAttribute('data-payload');
1996
+ const payload = payloadStr ? JSON.parse(payloadStr) : {};
1997
+ this.widgetAction.emit({ actionName, payload });
1998
+ }
1999
+ catch (err) {
2000
+ console.error('Failed to parse widget action payload:', err);
2001
+ }
2002
+ }
2003
+ }
2004
+ }
2005
+ /**
2006
+ * Cleanup all resources including event listeners.
2007
+ */
2008
+ cleanup() {
2009
+ // Cleanup dynamic widgets first
2010
+ this.cleanupDynamicWidgets();
2011
+ // Cleanup event manager
2012
+ this.eventManager = undefined;
2013
+ }
2014
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: WidgetRendererComponent, deps: [{ token: SSR_WIDGET_RENDERER }, { token: ANGULAR_WIDGET_RENDERER, optional: true }, { token: WIDGET_EVENT_MANAGER_FACTORY }, { token: WidgetRegistryService }, { token: i0.Injector }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Component });
2015
+ 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: [
2016
+ { provide: WIDGET_EVENT_MANAGER_FACTORY, useFactory: widgetEventManagerFactoryProvider },
2017
+ { provide: SSR_WIDGET_RENDERER, useFactory: ssrWidgetRendererFactory },
2018
+ { provide: ANGULAR_WIDGET_RENDERER, useFactory: angularWidgetRendererFactory },
2019
+ ], viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["widgetContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
2020
+ <div #widgetContainer class="bbq-widgets-container" (click)="handleClick($event)">
2021
+ @for (item of widgetItems; track item.index) {
2022
+ @if (item.isHtml) {
2023
+ <div class="bbq-widget" [innerHTML]="item.html"></div>
2024
+ } @else {
2025
+ <div class="bbq-widget" #dynamicWidget></div>
2026
+ }
2027
+ }
2028
+ </div>
2029
+ `, isInline: true, styles: [".bbq-widgets-container{margin-top:.5rem}.bbq-widget{margin-bottom:.5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2030
+ }
2031
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: WidgetRendererComponent, decorators: [{
2032
+ type: Component,
2033
+ args: [{ selector: 'bbq-widget-renderer', standalone: true, imports: [CommonModule], providers: [
2034
+ { provide: WIDGET_EVENT_MANAGER_FACTORY, useFactory: widgetEventManagerFactoryProvider },
2035
+ { provide: SSR_WIDGET_RENDERER, useFactory: ssrWidgetRendererFactory },
2036
+ { provide: ANGULAR_WIDGET_RENDERER, useFactory: angularWidgetRendererFactory },
2037
+ ], template: `
2038
+ <div #widgetContainer class="bbq-widgets-container" (click)="handleClick($event)">
2039
+ @for (item of widgetItems; track item.index) {
2040
+ @if (item.isHtml) {
2041
+ <div class="bbq-widget" [innerHTML]="item.html"></div>
2042
+ } @else {
2043
+ <div class="bbq-widget" #dynamicWidget></div>
2044
+ }
2045
+ }
2046
+ </div>
2047
+ `, styles: [".bbq-widgets-container{margin-top:.5rem}.bbq-widget{margin-bottom:.5rem}\n"] }]
2048
+ }], ctorParameters: () => [{ type: i2$1.SsrWidgetRenderer, decorators: [{
2049
+ type: Inject,
2050
+ args: [SSR_WIDGET_RENDERER]
2051
+ }] }, { type: AngularWidgetRenderer, decorators: [{
2052
+ type: Optional
2053
+ }, {
2054
+ type: Inject,
2055
+ args: [ANGULAR_WIDGET_RENDERER]
2056
+ }] }, { type: undefined, decorators: [{
2057
+ type: Inject,
2058
+ args: [WIDGET_EVENT_MANAGER_FACTORY]
2059
+ }] }, { type: WidgetRegistryService }, { type: i0.Injector }, { type: i0.EnvironmentInjector }], propDecorators: { widgets: [{
2060
+ type: Input
2061
+ }], widgetAction: [{
2062
+ type: Output
2063
+ }], containerRef: [{
2064
+ type: ViewChild,
2065
+ args: ['widgetContainer', { static: false }]
2066
+ }] } });
2067
+
2068
+ /**
2069
+ * Angular widget renderers
2070
+ */
2071
+
2072
+ class FormValidationListenerComponent {
2073
+ svc;
2074
+ formId;
2075
+ lastEvent = null;
2076
+ sub;
2077
+ constructor(svc) {
2078
+ this.svc = svc;
2079
+ }
2080
+ ngOnInit() {
2081
+ this.sub = this.svc.validation$.subscribe(ev => {
2082
+ if (!this.formId || ev.formId === this.formId) {
2083
+ this.lastEvent = ev;
2084
+ }
2085
+ });
2086
+ }
2087
+ ngOnDestroy() {
2088
+ this.sub?.unsubscribe();
2089
+ }
2090
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FormValidationListenerComponent, deps: [{ token: FormValidationService }], target: i0.ɵɵFactoryTarget.Component });
2091
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: FormValidationListenerComponent, isStandalone: true, selector: "bbq-form-validation-listener", inputs: { formId: "formId" }, providers: [
2092
+ { provide: ANGULAR_WIDGET_RENDERER, useFactory: angularWidgetRendererFactory },
2093
+ ], ngImport: i0, template: `
2094
+ <div class="bbq-validation-listener">
2095
+ @if (lastEvent) {
2096
+ <div>
2097
+ <strong>Last validation (formId: {{ lastEvent.formId }})</strong>
2098
+ <pre>{{ lastEvent | json }}</pre>
2099
+ </div>
2100
+ } @else {
2101
+ <small>No validation events yet.</small>
2102
+ }
2103
+ </div>
2104
+ `, isInline: true, dependencies: [{ kind: "pipe", type: JsonPipe, name: "json" }] });
2105
+ }
2106
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FormValidationListenerComponent, decorators: [{
2107
+ type: Component,
2108
+ args: [{
2109
+ selector: 'bbq-form-validation-listener',
2110
+ standalone: true,
2111
+ imports: [JsonPipe],
2112
+ providers: [
2113
+ { provide: ANGULAR_WIDGET_RENDERER, useFactory: angularWidgetRendererFactory },
2114
+ ],
2115
+ template: `
2116
+ <div class="bbq-validation-listener">
2117
+ @if (lastEvent) {
2118
+ <div>
2119
+ <strong>Last validation (formId: {{ lastEvent.formId }})</strong>
2120
+ <pre>{{ lastEvent | json }}</pre>
2121
+ </div>
2122
+ } @else {
2123
+ <small>No validation events yet.</small>
2124
+ }
2125
+ </div>
2126
+ `,
2127
+ }]
2128
+ }], ctorParameters: () => [{ type: FormValidationService }], propDecorators: { formId: [{
2129
+ type: Input
2130
+ }] } });
2131
+
2132
+ /**
2133
+ * @bbq-chat/widgets-angular
2134
+ *
2135
+ * Angular components and services for BbQ ChatWidgets
2136
+ *
2137
+ * This package provides Angular-native components and services that wrap
2138
+ * the core @bbq-chat/widgets library, making it easy to integrate chat
2139
+ * widgets into Angular applications.
2140
+ *
2141
+ * @packageDocumentation
2142
+ */
2143
+ // Export components
2144
+ // Version
2145
+ const VERSION = '1.0.11';
2146
+
2147
+ /**
2148
+ * Generated bundle index. Do not edit.
2149
+ */
2150
+
2151
+ export { ANGULAR_WIDGET_RENDERER, AngularWidgetRenderer, BUILT_IN_WIDGET_COMPONENTS, ButtonWidgetComponent, CardWidgetComponent, DatePickerWidgetComponent, DropdownWidgetComponent, FileUploadWidgetComponent, FormValidationListenerComponent, FormValidationService, FormWidgetComponent, ImageCollectionWidgetComponent, ImageWidgetComponent, InputWidgetComponent, MultiSelectWidgetComponent, ProgressBarWidgetComponent, SSR_WIDGET_RENDERER, SliderWidgetComponent, TextAreaWidgetComponent, ThemeSwitcherWidgetComponent, ToggleWidgetComponent, VERSION, WIDGET_EVENT_MANAGER_FACTORY, WidgetRegistryService, WidgetRendererComponent, angularWidgetRendererFactory, isComponentRenderer, isHtmlRenderer, isTemplateRenderer, ssrWidgetRendererFactory, widgetEventManagerFactoryProvider };
2152
+ //# sourceMappingURL=index.mjs.map