@bbq-chat/widgets-angular 1.0.9 → 1.0.10

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