@bbq-chat/widgets-angular 1.0.4 → 1.0.5

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.
@@ -1,9 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, EventEmitter, createComponent, ViewChild, Output, Input, Inject, Component } from '@angular/core';
2
+ import { Input, Component, createComponent, ViewContainerRef, ViewChildren, InjectionToken, Injectable, EventEmitter, ViewChild, Output, Inject, Optional } from '@angular/core';
3
+ import * as i1 from '@angular/common';
3
4
  import { CommonModule } from '@angular/common';
4
- import * as i2 from '@bbq-chat/widgets';
5
+ import * as i2$1 from '@bbq-chat/widgets';
5
6
  import { WidgetEventManager, SsrWidgetRenderer, customWidgetRegistry } from '@bbq-chat/widgets';
6
7
  export { ChatWidget, SsrWidgetRenderer, WidgetEventManager, customWidgetRegistry } from '@bbq-chat/widgets';
8
+ import * as i2 from '@angular/forms';
9
+ import { FormsModule } from '@angular/forms';
7
10
 
8
11
  /**
9
12
  * Type guard to check if a renderer is a TemplateRef
@@ -48,6 +51,1391 @@ function isHtmlRenderer(renderer) {
48
51
  return typeof renderer === 'function';
49
52
  }
50
53
 
54
+ /**
55
+ * Angular widget renderer
56
+ * Returns Angular component types for dynamic rendering
57
+ * Provides feature parity with SsrWidgetRenderer but uses Angular components
58
+ */
59
+ class AngularWidgetRenderer {
60
+ framework = 'Angular';
61
+ overrides;
62
+ componentRegistry = new Map();
63
+ constructor(options) {
64
+ this.overrides = options?.components;
65
+ }
66
+ /**
67
+ * Register all built-in widget components
68
+ * Must be called after components are imported to avoid circular dependencies
69
+ */
70
+ registerBuiltInComponents(components) {
71
+ for (const [type, component] of Object.entries(components)) {
72
+ this.componentRegistry.set(type, component);
73
+ }
74
+ }
75
+ /**
76
+ * Register or override a widget component
77
+ * Use this to replace built-in components or add custom ones
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * renderer.registerComponent('button', MyCustomButtonComponent);
82
+ * ```
83
+ */
84
+ registerComponent(type, component) {
85
+ this.componentRegistry.set(type, component);
86
+ }
87
+ /**
88
+ * Register multiple widget components at once
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * renderer.registerComponents({
93
+ * button: MyButtonComponent,
94
+ * card: MyCardComponent
95
+ * });
96
+ * ```
97
+ */
98
+ registerComponents(components) {
99
+ for (const [type, component] of Object.entries(components)) {
100
+ this.componentRegistry.set(type, component);
101
+ }
102
+ }
103
+ /**
104
+ * Get the Angular component type for a given widget
105
+ * Returns the component class that should be dynamically instantiated
106
+ */
107
+ getComponentType(widget) {
108
+ const type = widget.type;
109
+ // Check for custom override first
110
+ if (this.overrides && this.overrides[type]) {
111
+ return this.overrides[type];
112
+ }
113
+ // Check built-in registry
114
+ if (this.componentRegistry.has(type)) {
115
+ return this.componentRegistry.get(type);
116
+ }
117
+ return null;
118
+ }
119
+ /**
120
+ * Legacy method for IWidgetRenderer interface compatibility
121
+ * Not used in Angular rendering but required by interface
122
+ * @deprecated Use getComponentType() instead for Angular rendering
123
+ */
124
+ renderWidget(widget) {
125
+ // This method is not used in Angular rendering
126
+ // It's only here for interface compatibility
127
+ return `<!-- Angular component rendering for ${widget.type} -->`;
128
+ }
129
+ }
130
+
131
+ class ButtonWidgetComponent {
132
+ widget;
133
+ widgetAction;
134
+ get buttonWidget() {
135
+ return this.widget;
136
+ }
137
+ onClick() {
138
+ if (this.widgetAction) {
139
+ this.widgetAction(this.buttonWidget.action, {});
140
+ }
141
+ }
142
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ButtonWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
143
+ 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: `
144
+ <button
145
+ class="bbq-widget bbq-button"
146
+ [attr.data-widget-type]="'button'"
147
+ [attr.data-action]="buttonWidget.action"
148
+ type="button"
149
+ (click)="onClick()">
150
+ {{ buttonWidget.label }}
151
+ </button>
152
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
153
+ }
154
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ButtonWidgetComponent, decorators: [{
155
+ type: Component,
156
+ args: [{ selector: 'bbq-button-widget', standalone: true, imports: [CommonModule], template: `
157
+ <button
158
+ class="bbq-widget bbq-button"
159
+ [attr.data-widget-type]="'button'"
160
+ [attr.data-action]="buttonWidget.action"
161
+ type="button"
162
+ (click)="onClick()">
163
+ {{ buttonWidget.label }}
164
+ </button>
165
+ ` }]
166
+ }], propDecorators: { widget: [{
167
+ type: Input
168
+ }] } });
169
+
170
+ class CardWidgetComponent {
171
+ widget;
172
+ widgetAction;
173
+ get cardWidget() {
174
+ return this.widget;
175
+ }
176
+ onClick() {
177
+ if (this.widgetAction) {
178
+ this.widgetAction(this.cardWidget.action, {});
179
+ }
180
+ }
181
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: CardWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
182
+ 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: `
183
+ <div
184
+ class="bbq-widget bbq-card"
185
+ [attr.data-widget-type]="'card'"
186
+ [attr.data-action]="cardWidget.action"
187
+ role="article">
188
+ <h3 class="bbq-card-title">{{ cardWidget.title }}</h3>
189
+ @if (cardWidget.description) {
190
+ <p class="bbq-card-description">{{ cardWidget.description }}</p>
191
+ }
192
+ @if (cardWidget.imageUrl) {
193
+ <img
194
+ class="bbq-card-image"
195
+ [src]="cardWidget.imageUrl"
196
+ [alt]="cardWidget.title"
197
+ loading="lazy"
198
+ style="display:block;max-width:100%;height:auto;object-fit:cover;max-height:200px;border-radius:6px;margin-bottom:12px;" />
199
+ }
200
+ <button
201
+ class="bbq-card-action bbq-button"
202
+ [attr.data-action]="cardWidget.action"
203
+ type="button"
204
+ (click)="onClick()">
205
+ {{ cardWidget.label }}
206
+ </button>
207
+ </div>
208
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
209
+ }
210
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: CardWidgetComponent, decorators: [{
211
+ type: Component,
212
+ args: [{ selector: 'bbq-card-widget', standalone: true, imports: [CommonModule], template: `
213
+ <div
214
+ class="bbq-widget bbq-card"
215
+ [attr.data-widget-type]="'card'"
216
+ [attr.data-action]="cardWidget.action"
217
+ role="article">
218
+ <h3 class="bbq-card-title">{{ cardWidget.title }}</h3>
219
+ @if (cardWidget.description) {
220
+ <p class="bbq-card-description">{{ cardWidget.description }}</p>
221
+ }
222
+ @if (cardWidget.imageUrl) {
223
+ <img
224
+ class="bbq-card-image"
225
+ [src]="cardWidget.imageUrl"
226
+ [alt]="cardWidget.title"
227
+ loading="lazy"
228
+ style="display:block;max-width:100%;height:auto;object-fit:cover;max-height:200px;border-radius:6px;margin-bottom:12px;" />
229
+ }
230
+ <button
231
+ class="bbq-card-action bbq-button"
232
+ [attr.data-action]="cardWidget.action"
233
+ type="button"
234
+ (click)="onClick()">
235
+ {{ cardWidget.label }}
236
+ </button>
237
+ </div>
238
+ ` }]
239
+ }], propDecorators: { widget: [{
240
+ type: Input
241
+ }] } });
242
+
243
+ class InputWidgetComponent {
244
+ widget;
245
+ widgetAction;
246
+ value = '';
247
+ inputId = '';
248
+ get inputWidget() {
249
+ return this.widget;
250
+ }
251
+ get showLabel() {
252
+ const widget = this.inputWidget;
253
+ if (widget.hideLabel === true) {
254
+ return false;
255
+ }
256
+ if (widget.showLabel === false) {
257
+ return false;
258
+ }
259
+ return true;
260
+ }
261
+ get inputClasses() {
262
+ return this.isFormAppearance ? ['bbq-form-input'] : ['bbq-input'];
263
+ }
264
+ get isFormAppearance() {
265
+ return this.inputWidget.appearance === 'form';
266
+ }
267
+ ngOnInit() {
268
+ this.inputId = `bbq-${this.inputWidget.action.replace(/\s+/g, '-').toLowerCase()}-input`;
269
+ }
270
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: InputWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
271
+ 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: `
272
+ <div
273
+ class="bbq-widget bbq-input"
274
+ [attr.data-widget-type]="'input'">
275
+ <label *ngIf="showLabel" class="bbq-input-label" [attr.for]="inputId">
276
+ {{ inputWidget.label }}
277
+ </label>
278
+ <input
279
+ type="text"
280
+ [id]="inputId"
281
+ [ngClass]="inputClasses"
282
+ [attr.data-action]="inputWidget.action"
283
+ [placeholder]="inputWidget.placeholder || ''"
284
+ [maxLength]="inputWidget.maxLength || 0"
285
+ [(ngModel)]="value" />
286
+ </div>
287
+ `, 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"] }] });
288
+ }
289
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: InputWidgetComponent, decorators: [{
290
+ type: Component,
291
+ args: [{ selector: 'bbq-input-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
292
+ <div
293
+ class="bbq-widget bbq-input"
294
+ [attr.data-widget-type]="'input'">
295
+ <label *ngIf="showLabel" class="bbq-input-label" [attr.for]="inputId">
296
+ {{ inputWidget.label }}
297
+ </label>
298
+ <input
299
+ type="text"
300
+ [id]="inputId"
301
+ [ngClass]="inputClasses"
302
+ [attr.data-action]="inputWidget.action"
303
+ [placeholder]="inputWidget.placeholder || ''"
304
+ [maxLength]="inputWidget.maxLength || 0"
305
+ [(ngModel)]="value" />
306
+ </div>
307
+ ` }]
308
+ }], propDecorators: { widget: [{
309
+ type: Input
310
+ }] } });
311
+
312
+ class TextAreaWidgetComponent {
313
+ widget;
314
+ widgetAction;
315
+ value = '';
316
+ textareaId = '';
317
+ get textareaWidget() {
318
+ return this.widget;
319
+ }
320
+ get showLabel() {
321
+ const widget = this.textareaWidget;
322
+ if (widget.hideLabel === true) {
323
+ return false;
324
+ }
325
+ if (widget.showLabel === false) {
326
+ return false;
327
+ }
328
+ return true;
329
+ }
330
+ get textareaClasses() {
331
+ return this.isFormAppearance ? ['bbq-form-textarea'] : ['bbq-form-textarea', 'bbq-input'];
332
+ }
333
+ get isFormAppearance() {
334
+ return this.textareaWidget.appearance === 'form';
335
+ }
336
+ ngOnInit() {
337
+ this.textareaId = `bbq-${this.textareaWidget.action.replace(/\s+/g, '-').toLowerCase()}-textarea`;
338
+ }
339
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: TextAreaWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
340
+ 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: `
341
+ <div
342
+ class="bbq-widget bbq-textarea"
343
+ [attr.data-widget-type]="'textarea'">
344
+ <label *ngIf="showLabel" class="bbq-textarea-label" [attr.for]="textareaId">
345
+ {{ textareaWidget.label }}
346
+ </label>
347
+ <textarea
348
+ [id]="textareaId"
349
+ [ngClass]="textareaClasses"
350
+ [attr.data-action]="textareaWidget.action"
351
+ [placeholder]="textareaWidget.placeholder || ''"
352
+ [maxLength]="textareaWidget.maxLength || 0"
353
+ [rows]="textareaWidget.rows || 4"
354
+ [(ngModel)]="value"></textarea>
355
+ </div>
356
+ `, 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"] }] });
357
+ }
358
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: TextAreaWidgetComponent, decorators: [{
359
+ type: Component,
360
+ args: [{ selector: 'bbq-textarea-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
361
+ <div
362
+ class="bbq-widget bbq-textarea"
363
+ [attr.data-widget-type]="'textarea'">
364
+ <label *ngIf="showLabel" class="bbq-textarea-label" [attr.for]="textareaId">
365
+ {{ textareaWidget.label }}
366
+ </label>
367
+ <textarea
368
+ [id]="textareaId"
369
+ [ngClass]="textareaClasses"
370
+ [attr.data-action]="textareaWidget.action"
371
+ [placeholder]="textareaWidget.placeholder || ''"
372
+ [maxLength]="textareaWidget.maxLength || 0"
373
+ [rows]="textareaWidget.rows || 4"
374
+ [(ngModel)]="value"></textarea>
375
+ </div>
376
+ ` }]
377
+ }], propDecorators: { widget: [{
378
+ type: Input
379
+ }] } });
380
+
381
+ class DropdownWidgetComponent {
382
+ widget;
383
+ widgetAction;
384
+ value = '';
385
+ selectId = '';
386
+ get dropdownWidget() {
387
+ return this.widget;
388
+ }
389
+ get showLabel() {
390
+ const widget = this.dropdownWidget;
391
+ if (widget.hideLabel === true) {
392
+ return false;
393
+ }
394
+ if (widget.showLabel === false) {
395
+ return false;
396
+ }
397
+ return true;
398
+ }
399
+ get selectClasses() {
400
+ return this.isFormAppearance ? ['bbq-form-select'] : ['bbq-dropdown'];
401
+ }
402
+ get isFormAppearance() {
403
+ return this.dropdownWidget.appearance === 'form';
404
+ }
405
+ ngOnInit() {
406
+ this.selectId = `bbq-${this.dropdownWidget.action.replace(/\s+/g, '-').toLowerCase()}-select`;
407
+ this.value = this.dropdownWidget.options[0] || '';
408
+ }
409
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DropdownWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
410
+ 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: `
411
+ <div
412
+ class="bbq-widget bbq-dropdown"
413
+ [attr.data-widget-type]="'dropdown'">
414
+ <label *ngIf="showLabel" class="bbq-dropdown-label" [attr.for]="selectId">
415
+ {{ dropdownWidget.label }}
416
+ </label>
417
+ <select
418
+ [id]="selectId"
419
+ [ngClass]="selectClasses"
420
+ [attr.data-action]="dropdownWidget.action"
421
+ [(ngModel)]="value">
422
+ @for (option of dropdownWidget.options; track option) {
423
+ <option [value]="option">{{ option }}</option>
424
+ }
425
+ </select>
426
+ </div>
427
+ `, 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"] }] });
428
+ }
429
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DropdownWidgetComponent, decorators: [{
430
+ type: Component,
431
+ args: [{ selector: 'bbq-dropdown-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
432
+ <div
433
+ class="bbq-widget bbq-dropdown"
434
+ [attr.data-widget-type]="'dropdown'">
435
+ <label *ngIf="showLabel" class="bbq-dropdown-label" [attr.for]="selectId">
436
+ {{ dropdownWidget.label }}
437
+ </label>
438
+ <select
439
+ [id]="selectId"
440
+ [ngClass]="selectClasses"
441
+ [attr.data-action]="dropdownWidget.action"
442
+ [(ngModel)]="value">
443
+ @for (option of dropdownWidget.options; track option) {
444
+ <option [value]="option">{{ option }}</option>
445
+ }
446
+ </select>
447
+ </div>
448
+ ` }]
449
+ }], propDecorators: { widget: [{
450
+ type: Input
451
+ }] } });
452
+
453
+ class SliderWidgetComponent {
454
+ widget;
455
+ widgetAction;
456
+ value = 0;
457
+ sliderId = '';
458
+ get sliderWidget() {
459
+ return this.widget;
460
+ }
461
+ get showLabel() {
462
+ const widget = this.sliderWidget;
463
+ if (widget.hideLabel === true) {
464
+ return false;
465
+ }
466
+ if (widget.showLabel === false) {
467
+ return false;
468
+ }
469
+ return true;
470
+ }
471
+ get sliderClasses() {
472
+ return this.isFormAppearance ? ['bbq-form-slider'] : ['bbq-slider'];
473
+ }
474
+ get isFormAppearance() {
475
+ return this.sliderWidget.appearance === 'form';
476
+ }
477
+ ngOnInit() {
478
+ this.sliderId = `bbq-${this.sliderWidget.action.replace(/\s+/g, '-').toLowerCase()}-slider`;
479
+ this.value = this.sliderWidget.defaultValue ?? this.sliderWidget.min;
480
+ }
481
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SliderWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
482
+ 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: `
483
+ <div
484
+ class="bbq-widget bbq-slider"
485
+ [attr.data-widget-type]="'slider'">
486
+ <label *ngIf="showLabel" class="bbq-slider-label" [attr.for]="sliderId">
487
+ {{ sliderWidget.label }}
488
+ </label>
489
+ <input
490
+ type="range"
491
+ [id]="sliderId"
492
+ [ngClass]="sliderClasses"
493
+ [min]="sliderWidget.min"
494
+ [max]="sliderWidget.max"
495
+ [step]="sliderWidget.step"
496
+ [attr.data-action]="sliderWidget.action"
497
+ [attr.aria-label]="sliderWidget.label"
498
+ [(ngModel)]="value" />
499
+ <span class="bbq-slider-value" aria-live="polite">{{ value }}</span>
500
+ </div>
501
+ `, 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"] }] });
502
+ }
503
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SliderWidgetComponent, decorators: [{
504
+ type: Component,
505
+ args: [{ selector: 'bbq-slider-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
506
+ <div
507
+ class="bbq-widget bbq-slider"
508
+ [attr.data-widget-type]="'slider'">
509
+ <label *ngIf="showLabel" class="bbq-slider-label" [attr.for]="sliderId">
510
+ {{ sliderWidget.label }}
511
+ </label>
512
+ <input
513
+ type="range"
514
+ [id]="sliderId"
515
+ [ngClass]="sliderClasses"
516
+ [min]="sliderWidget.min"
517
+ [max]="sliderWidget.max"
518
+ [step]="sliderWidget.step"
519
+ [attr.data-action]="sliderWidget.action"
520
+ [attr.aria-label]="sliderWidget.label"
521
+ [(ngModel)]="value" />
522
+ <span class="bbq-slider-value" aria-live="polite">{{ value }}</span>
523
+ </div>
524
+ ` }]
525
+ }], propDecorators: { widget: [{
526
+ type: Input
527
+ }] } });
528
+
529
+ class ToggleWidgetComponent {
530
+ widget;
531
+ widgetAction;
532
+ checked = false;
533
+ checkboxId = '';
534
+ get toggleWidget() {
535
+ return this.widget;
536
+ }
537
+ get showLabel() {
538
+ const widget = this.toggleWidget;
539
+ if (widget.hideLabel === true) {
540
+ return false;
541
+ }
542
+ if (widget.showLabel === false) {
543
+ return false;
544
+ }
545
+ return true;
546
+ }
547
+ get checkboxClasses() {
548
+ return this.isFormAppearance ? ['bbq-toggle-input', 'bbq-form-toggle'] : ['bbq-toggle-input'];
549
+ }
550
+ get isFormAppearance() {
551
+ return this.toggleWidget.appearance === 'form';
552
+ }
553
+ ngOnInit() {
554
+ this.checkboxId = `bbq-${this.toggleWidget.action.replace(/\s+/g, '-').toLowerCase()}-checkbox`;
555
+ this.checked = this.toggleWidget.defaultValue ?? false;
556
+ }
557
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ToggleWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
558
+ 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: `
559
+ <div
560
+ class="bbq-widget bbq-toggle"
561
+ [attr.data-widget-type]="'toggle'">
562
+ <label class="bbq-toggle-label" [attr.for]="checkboxId">
563
+ <input
564
+ type="checkbox"
565
+ [id]="checkboxId"
566
+ [ngClass]="checkboxClasses"
567
+ [attr.data-action]="toggleWidget.action"
568
+ [attr.aria-label]="toggleWidget.label"
569
+ [(ngModel)]="checked" />
570
+ <span *ngIf="showLabel" class="bbq-toggle-text">{{ toggleWidget.label }}</span>
571
+ </label>
572
+ </div>
573
+ `, 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"] }] });
574
+ }
575
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ToggleWidgetComponent, decorators: [{
576
+ type: Component,
577
+ args: [{ selector: 'bbq-toggle-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
578
+ <div
579
+ class="bbq-widget bbq-toggle"
580
+ [attr.data-widget-type]="'toggle'">
581
+ <label class="bbq-toggle-label" [attr.for]="checkboxId">
582
+ <input
583
+ type="checkbox"
584
+ [id]="checkboxId"
585
+ [ngClass]="checkboxClasses"
586
+ [attr.data-action]="toggleWidget.action"
587
+ [attr.aria-label]="toggleWidget.label"
588
+ [(ngModel)]="checked" />
589
+ <span *ngIf="showLabel" class="bbq-toggle-text">{{ toggleWidget.label }}</span>
590
+ </label>
591
+ </div>
592
+ ` }]
593
+ }], propDecorators: { widget: [{
594
+ type: Input
595
+ }] } });
596
+
597
+ class FileUploadWidgetComponent {
598
+ widget;
599
+ widgetAction;
600
+ inputId = '';
601
+ get fileUploadWidget() {
602
+ return this.widget;
603
+ }
604
+ get showLabel() {
605
+ const widget = this.fileUploadWidget;
606
+ if (widget.hideLabel === true) {
607
+ return false;
608
+ }
609
+ if (widget.showLabel === false) {
610
+ return false;
611
+ }
612
+ return true;
613
+ }
614
+ get inputClasses() {
615
+ return this.isFormAppearance ? ['bbq-form-fileupload'] : ['bbq-file'];
616
+ }
617
+ get isFormAppearance() {
618
+ return this.fileUploadWidget.appearance === 'form';
619
+ }
620
+ ngOnInit() {
621
+ this.inputId = `bbq-${this.fileUploadWidget.action.replace(/\s+/g, '-').toLowerCase()}-file`;
622
+ }
623
+ onFileChange(event) {
624
+ const target = event.target;
625
+ if (target.files && target.files.length > 0) {
626
+ const file = target.files[0];
627
+ if (this.widgetAction) {
628
+ this.widgetAction(this.fileUploadWidget.action, { file });
629
+ }
630
+ }
631
+ }
632
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FileUploadWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
633
+ 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: `
634
+ <div
635
+ class="bbq-widget bbq-file-upload"
636
+ [attr.data-widget-type]="'fileupload'">
637
+ <label *ngIf="showLabel" class="bbq-file-label" [attr.for]="inputId">
638
+ {{ fileUploadWidget.label }}
639
+ </label>
640
+ <input
641
+ type="file"
642
+ [id]="inputId"
643
+ [ngClass]="inputClasses"
644
+ [attr.data-action]="fileUploadWidget.action"
645
+ [accept]="fileUploadWidget.accept || ''"
646
+ [attr.data-max-bytes]="fileUploadWidget.maxBytes"
647
+ (change)="onFileChange($event)" />
648
+ </div>
649
+ `, 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"] }] });
650
+ }
651
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FileUploadWidgetComponent, decorators: [{
652
+ type: Component,
653
+ args: [{ selector: 'bbq-fileupload-widget', standalone: true, imports: [CommonModule], template: `
654
+ <div
655
+ class="bbq-widget bbq-file-upload"
656
+ [attr.data-widget-type]="'fileupload'">
657
+ <label *ngIf="showLabel" class="bbq-file-label" [attr.for]="inputId">
658
+ {{ fileUploadWidget.label }}
659
+ </label>
660
+ <input
661
+ type="file"
662
+ [id]="inputId"
663
+ [ngClass]="inputClasses"
664
+ [attr.data-action]="fileUploadWidget.action"
665
+ [accept]="fileUploadWidget.accept || ''"
666
+ [attr.data-max-bytes]="fileUploadWidget.maxBytes"
667
+ (change)="onFileChange($event)" />
668
+ </div>
669
+ ` }]
670
+ }], propDecorators: { widget: [{
671
+ type: Input
672
+ }] } });
673
+
674
+ class ThemeSwitcherWidgetComponent {
675
+ widget;
676
+ widgetAction;
677
+ value = '';
678
+ selectId = '';
679
+ get themeSwitcherWidget() {
680
+ return this.widget;
681
+ }
682
+ ngOnInit() {
683
+ this.selectId = `bbq-${this.themeSwitcherWidget.action.replace(/\s+/g, '-').toLowerCase()}-select`;
684
+ this.value = this.themeSwitcherWidget.themes[0] || '';
685
+ }
686
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ThemeSwitcherWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
687
+ 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: `
688
+ <div
689
+ class="bbq-widget bbq-theme-switcher"
690
+ [attr.data-widget-type]="'themeswitcher'">
691
+ <label class="bbq-theme-switcher-label" [attr.for]="selectId">
692
+ {{ themeSwitcherWidget.label }}
693
+ </label>
694
+ <select
695
+ [id]="selectId"
696
+ class="bbq-theme-switcher-select"
697
+ [attr.data-action]="themeSwitcherWidget.action"
698
+ [(ngModel)]="value">
699
+ @for (theme of themeSwitcherWidget.themes; track theme) {
700
+ <option [value]="theme">{{ theme }}</option>
701
+ }
702
+ </select>
703
+ </div>
704
+ `, 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"] }] });
705
+ }
706
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ThemeSwitcherWidgetComponent, decorators: [{
707
+ type: Component,
708
+ args: [{ selector: 'bbq-themeswitcher-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
709
+ <div
710
+ class="bbq-widget bbq-theme-switcher"
711
+ [attr.data-widget-type]="'themeswitcher'">
712
+ <label class="bbq-theme-switcher-label" [attr.for]="selectId">
713
+ {{ themeSwitcherWidget.label }}
714
+ </label>
715
+ <select
716
+ [id]="selectId"
717
+ class="bbq-theme-switcher-select"
718
+ [attr.data-action]="themeSwitcherWidget.action"
719
+ [(ngModel)]="value">
720
+ @for (theme of themeSwitcherWidget.themes; track theme) {
721
+ <option [value]="theme">{{ theme }}</option>
722
+ }
723
+ </select>
724
+ </div>
725
+ ` }]
726
+ }], propDecorators: { widget: [{
727
+ type: Input
728
+ }] } });
729
+
730
+ class DatePickerWidgetComponent {
731
+ widget;
732
+ widgetAction;
733
+ value = '';
734
+ inputId = '';
735
+ get datePickerWidget() {
736
+ return this.widget;
737
+ }
738
+ get showLabel() {
739
+ const widget = this.datePickerWidget;
740
+ if (widget.hideLabel === true) {
741
+ return false;
742
+ }
743
+ if (widget.showLabel === false) {
744
+ return false;
745
+ }
746
+ return true;
747
+ }
748
+ get inputClasses() {
749
+ return this.isFormAppearance ? ['bbq-form-datepicker'] : ['bbq-form-datepicker', 'bbq-input'];
750
+ }
751
+ get isFormAppearance() {
752
+ return this.datePickerWidget.appearance === 'form';
753
+ }
754
+ ngOnInit() {
755
+ this.inputId = `bbq-${this.datePickerWidget.action.replace(/\s+/g, '-').toLowerCase()}-date`;
756
+ }
757
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DatePickerWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
758
+ 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: `
759
+ <div
760
+ class="bbq-widget bbq-date-picker"
761
+ [attr.data-widget-type]="'datepicker'">
762
+ <label *ngIf="showLabel" class="bbq-date-picker-label" [attr.for]="inputId">
763
+ {{ datePickerWidget.label }}
764
+ </label>
765
+ <input
766
+ type="date"
767
+ [id]="inputId"
768
+ [ngClass]="inputClasses"
769
+ [attr.data-action]="datePickerWidget.action"
770
+ [min]="datePickerWidget.minDate || ''"
771
+ [max]="datePickerWidget.maxDate || ''"
772
+ [(ngModel)]="value" />
773
+ </div>
774
+ `, 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"] }] });
775
+ }
776
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DatePickerWidgetComponent, decorators: [{
777
+ type: Component,
778
+ args: [{ selector: 'bbq-datepicker-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
779
+ <div
780
+ class="bbq-widget bbq-date-picker"
781
+ [attr.data-widget-type]="'datepicker'">
782
+ <label *ngIf="showLabel" class="bbq-date-picker-label" [attr.for]="inputId">
783
+ {{ datePickerWidget.label }}
784
+ </label>
785
+ <input
786
+ type="date"
787
+ [id]="inputId"
788
+ [ngClass]="inputClasses"
789
+ [attr.data-action]="datePickerWidget.action"
790
+ [min]="datePickerWidget.minDate || ''"
791
+ [max]="datePickerWidget.maxDate || ''"
792
+ [(ngModel)]="value" />
793
+ </div>
794
+ ` }]
795
+ }], propDecorators: { widget: [{
796
+ type: Input
797
+ }] } });
798
+
799
+ class MultiSelectWidgetComponent {
800
+ widget;
801
+ widgetAction;
802
+ values = [];
803
+ selectId = '';
804
+ get multiSelectWidget() {
805
+ return this.widget;
806
+ }
807
+ get showLabel() {
808
+ const widget = this.multiSelectWidget;
809
+ if (widget.hideLabel === true) {
810
+ return false;
811
+ }
812
+ if (widget.showLabel === false) {
813
+ return false;
814
+ }
815
+ return true;
816
+ }
817
+ get selectClasses() {
818
+ return this.isFormAppearance
819
+ ? ['bbq-form-multiselect', 'bbq-form-select']
820
+ : ['bbq-form-multiselect', 'bbq-form-select'];
821
+ }
822
+ get isFormAppearance() {
823
+ return this.multiSelectWidget.appearance === 'form';
824
+ }
825
+ ngOnInit() {
826
+ this.selectId = `bbq-${this.multiSelectWidget.action.replace(/\s+/g, '-').toLowerCase()}-select`;
827
+ }
828
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MultiSelectWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
829
+ 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: `
830
+ <div
831
+ class="bbq-widget bbq-multi-select"
832
+ [attr.data-widget-type]="'multiselect'">
833
+ <label *ngIf="showLabel" class="bbq-multi-select-label" [attr.for]="selectId">
834
+ {{ multiSelectWidget.label }}
835
+ </label>
836
+ <select
837
+ [id]="selectId"
838
+ [ngClass]="selectClasses"
839
+ [attr.data-action]="multiSelectWidget.action"
840
+ multiple
841
+ [(ngModel)]="values">
842
+ @for (option of multiSelectWidget.options; track option) {
843
+ <option [value]="option">{{ option }}</option>
844
+ }
845
+ </select>
846
+ </div>
847
+ `, 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"] }] });
848
+ }
849
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MultiSelectWidgetComponent, decorators: [{
850
+ type: Component,
851
+ args: [{ selector: 'bbq-multiselect-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
852
+ <div
853
+ class="bbq-widget bbq-multi-select"
854
+ [attr.data-widget-type]="'multiselect'">
855
+ <label *ngIf="showLabel" class="bbq-multi-select-label" [attr.for]="selectId">
856
+ {{ multiSelectWidget.label }}
857
+ </label>
858
+ <select
859
+ [id]="selectId"
860
+ [ngClass]="selectClasses"
861
+ [attr.data-action]="multiSelectWidget.action"
862
+ multiple
863
+ [(ngModel)]="values">
864
+ @for (option of multiSelectWidget.options; track option) {
865
+ <option [value]="option">{{ option }}</option>
866
+ }
867
+ </select>
868
+ </div>
869
+ ` }]
870
+ }], propDecorators: { widget: [{
871
+ type: Input
872
+ }] } });
873
+
874
+ class ProgressBarWidgetComponent {
875
+ widget;
876
+ widgetAction;
877
+ progressId = '';
878
+ percentage = 0;
879
+ get progressBarWidget() {
880
+ return this.widget;
881
+ }
882
+ ngOnInit() {
883
+ this.progressId = `bbq-${this.progressBarWidget.action.replace(/\s+/g, '-').toLowerCase()}-progress`;
884
+ const max = this.progressBarWidget.max;
885
+ this.percentage = max > 0 ? Math.floor((this.progressBarWidget.value * 100) / max) : 0;
886
+ }
887
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ProgressBarWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
888
+ 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: `
889
+ <div
890
+ class="bbq-widget bbq-progress-bar"
891
+ [attr.data-widget-type]="'progressbar'">
892
+ <label class="bbq-progress-bar-label" [attr.for]="progressId">
893
+ {{ progressBarWidget.label }}
894
+ </label>
895
+ <progress
896
+ [id]="progressId"
897
+ class="bbq-progress-bar-element"
898
+ [value]="progressBarWidget.value"
899
+ [max]="progressBarWidget.max"
900
+ [attr.data-action]="progressBarWidget.action"
901
+ [attr.aria-label]="progressBarWidget.label"
902
+ [attr.aria-valuenow]="progressBarWidget.value"
903
+ [attr.aria-valuemin]="0"
904
+ [attr.aria-valuemax]="progressBarWidget.max">
905
+ {{ percentage }}%
906
+ </progress>
907
+ <span class="bbq-progress-bar-value" aria-live="polite">{{ percentage }}%</span>
908
+ </div>
909
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
910
+ }
911
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ProgressBarWidgetComponent, decorators: [{
912
+ type: Component,
913
+ args: [{ selector: 'bbq-progressbar-widget', standalone: true, imports: [CommonModule], template: `
914
+ <div
915
+ class="bbq-widget bbq-progress-bar"
916
+ [attr.data-widget-type]="'progressbar'">
917
+ <label class="bbq-progress-bar-label" [attr.for]="progressId">
918
+ {{ progressBarWidget.label }}
919
+ </label>
920
+ <progress
921
+ [id]="progressId"
922
+ class="bbq-progress-bar-element"
923
+ [value]="progressBarWidget.value"
924
+ [max]="progressBarWidget.max"
925
+ [attr.data-action]="progressBarWidget.action"
926
+ [attr.aria-label]="progressBarWidget.label"
927
+ [attr.aria-valuenow]="progressBarWidget.value"
928
+ [attr.aria-valuemin]="0"
929
+ [attr.aria-valuemax]="progressBarWidget.max">
930
+ {{ percentage }}%
931
+ </progress>
932
+ <span class="bbq-progress-bar-value" aria-live="polite">{{ percentage }}%</span>
933
+ </div>
934
+ ` }]
935
+ }], propDecorators: { widget: [{
936
+ type: Input
937
+ }] } });
938
+
939
+ /**
940
+ * Helper class to wrap form fields as widgets for dynamic rendering
941
+ */
942
+ class FormFieldWidget {
943
+ field;
944
+ formId;
945
+ type;
946
+ label;
947
+ action;
948
+ appearance = 'form';
949
+ hideLabel = true;
950
+ constructor(field, formId) {
951
+ this.field = field;
952
+ this.formId = formId;
953
+ this.type = this.mapFieldTypeToWidgetType(field.type);
954
+ this.label = field.label;
955
+ this.action = `${formId}_${field.name}`;
956
+ }
957
+ mapFieldTypeToWidgetType(fieldType) {
958
+ const typeMap = {
959
+ 'input': 'input',
960
+ 'text': 'input',
961
+ 'email': 'input',
962
+ 'number': 'input',
963
+ 'password': 'input',
964
+ 'textarea': 'textarea',
965
+ 'dropdown': 'dropdown',
966
+ 'select': 'dropdown',
967
+ 'slider': 'slider',
968
+ 'toggle': 'toggle',
969
+ 'datepicker': 'datepicker',
970
+ 'multiselect': 'multiselect',
971
+ 'fileupload': 'fileupload'
972
+ };
973
+ return typeMap[fieldType] || 'input';
974
+ }
975
+ // Map field properties to widget properties
976
+ get placeholder() {
977
+ return this.field.placeholder ?? undefined;
978
+ }
979
+ get maxLength() {
980
+ return this.field['maxLength'];
981
+ }
982
+ get rows() {
983
+ return this.field['rows'];
984
+ }
985
+ get options() {
986
+ return this.field['options'] || [];
987
+ }
988
+ get min() {
989
+ return this.field['min'] ?? 0;
990
+ }
991
+ get max() {
992
+ return this.field['max'] ?? 100;
993
+ }
994
+ get step() {
995
+ return this.field['step'] ?? 1;
996
+ }
997
+ get defaultValue() {
998
+ return this.field['defaultValue'] ?? (this.type === 'slider' ? this.min : undefined);
999
+ }
1000
+ get minDate() {
1001
+ return this.field['minDate'];
1002
+ }
1003
+ get maxDate() {
1004
+ return this.field['maxDate'];
1005
+ }
1006
+ get accept() {
1007
+ return this.field['accept'];
1008
+ }
1009
+ get maxBytes() {
1010
+ return this.field['maxBytes'];
1011
+ }
1012
+ // ChatWidget interface methods
1013
+ toJson() {
1014
+ return JSON.stringify(this.toObject());
1015
+ }
1016
+ toObject() {
1017
+ return {
1018
+ type: this.type,
1019
+ label: this.label,
1020
+ action: this.action,
1021
+ ...this.field
1022
+ };
1023
+ }
1024
+ }
1025
+ class FormWidgetComponent {
1026
+ injector;
1027
+ environmentInjector;
1028
+ widget;
1029
+ widgetAction;
1030
+ fieldContainers;
1031
+ formId = '';
1032
+ formData = {};
1033
+ showValidationMessage = false;
1034
+ componentRefs = [];
1035
+ // Component registry for field types
1036
+ fieldComponentRegistry = {
1037
+ 'input': InputWidgetComponent,
1038
+ 'text': InputWidgetComponent,
1039
+ 'email': InputWidgetComponent,
1040
+ 'number': InputWidgetComponent,
1041
+ 'password': InputWidgetComponent,
1042
+ 'textarea': TextAreaWidgetComponent,
1043
+ 'dropdown': DropdownWidgetComponent,
1044
+ 'select': DropdownWidgetComponent,
1045
+ 'slider': SliderWidgetComponent,
1046
+ 'toggle': ToggleWidgetComponent,
1047
+ 'datepicker': DatePickerWidgetComponent,
1048
+ 'multiselect': MultiSelectWidgetComponent,
1049
+ 'fileupload': FileUploadWidgetComponent,
1050
+ 'checkbox': ToggleWidgetComponent,
1051
+ 'radio': ToggleWidgetComponent,
1052
+ };
1053
+ get formWidget() {
1054
+ return this.widget;
1055
+ }
1056
+ constructor(injector, environmentInjector) {
1057
+ this.injector = injector;
1058
+ this.environmentInjector = environmentInjector;
1059
+ }
1060
+ ngOnInit() {
1061
+ this.formId = `bbq-${this.formWidget.action.replace(/\s+/g, '-').toLowerCase()}`;
1062
+ // Initialize form data with default values
1063
+ for (const field of this.formWidget.fields || []) {
1064
+ if (field.type === 'slider') {
1065
+ this.formData[field.name] = field['default'] ?? field['defaultValue'] ?? field['min'] ?? 0;
1066
+ }
1067
+ else if (field.type === 'toggle' || field.type === 'checkbox') {
1068
+ this.formData[field.name] = field['defaultValue'] ?? false;
1069
+ }
1070
+ else if (field.type === 'multiselect') {
1071
+ this.formData[field.name] = [];
1072
+ }
1073
+ else {
1074
+ this.formData[field.name] = '';
1075
+ }
1076
+ }
1077
+ }
1078
+ ngAfterViewInit() {
1079
+ // Render field widgets dynamically
1080
+ setTimeout(() => this.renderFieldWidgets(), 0);
1081
+ }
1082
+ ngOnDestroy() {
1083
+ // Clean up component refs
1084
+ this.componentRefs.forEach(ref => ref.destroy());
1085
+ this.componentRefs = [];
1086
+ }
1087
+ renderFieldWidgets() {
1088
+ const containers = this.fieldContainers.toArray();
1089
+ const fields = this.formWidget.fields || [];
1090
+ fields.forEach((field, index) => {
1091
+ const container = containers[index];
1092
+ if (!container)
1093
+ return;
1094
+ const componentType = this.fieldComponentRegistry[field.type];
1095
+ if (!componentType) {
1096
+ // Fallback to input for unknown types
1097
+ this.renderInputFallback(container, field);
1098
+ return;
1099
+ }
1100
+ // Create the field widget
1101
+ const fieldWidget = new FormFieldWidget(field, this.formId);
1102
+ // Create the component
1103
+ const componentRef = createComponent(componentType, {
1104
+ environmentInjector: this.environmentInjector,
1105
+ elementInjector: this.injector,
1106
+ });
1107
+ // Set component inputs
1108
+ const instance = componentRef.instance;
1109
+ instance['widget'] = fieldWidget;
1110
+ // Connect to form data via widgetAction
1111
+ instance['widgetAction'] = (actionName, payload) => {
1112
+ // Handle field value changes - for now, we'll sync via the rendered widget's internal state
1113
+ // The actual form submission will gather values from the DOM
1114
+ };
1115
+ // Attach to container
1116
+ container.insert(componentRef.hostView);
1117
+ this.componentRefs.push(componentRef);
1118
+ // Trigger change detection
1119
+ componentRef.changeDetectorRef.detectChanges();
1120
+ });
1121
+ }
1122
+ renderInputFallback(container, field) {
1123
+ // For unsupported field types, render a basic input
1124
+ const fieldWidget = new FormFieldWidget(field, this.formId);
1125
+ const componentRef = createComponent(InputWidgetComponent, {
1126
+ environmentInjector: this.environmentInjector,
1127
+ elementInjector: this.injector,
1128
+ });
1129
+ const instance = componentRef.instance;
1130
+ instance['widget'] = fieldWidget;
1131
+ container.insert(componentRef.hostView);
1132
+ this.componentRefs.push(componentRef);
1133
+ componentRef.changeDetectorRef.detectChanges();
1134
+ }
1135
+ getFieldId(fieldName) {
1136
+ // Match the ID format used by dynamically rendered input widgets
1137
+ return `bbq-${this.formId}_${fieldName}-input`;
1138
+ }
1139
+ getFieldProp(field, prop) {
1140
+ return field[prop];
1141
+ }
1142
+ onActionClick(actionType) {
1143
+ if (actionType === 'submit') {
1144
+ // Validate required fields
1145
+ const hasErrors = this.validateForm();
1146
+ if (hasErrors) {
1147
+ this.showValidationMessage = true;
1148
+ return;
1149
+ }
1150
+ this.showValidationMessage = false;
1151
+ // Gather form data from the DOM (since widgets manage their own state)
1152
+ this.gatherFormData();
1153
+ if (this.widgetAction) {
1154
+ this.widgetAction(this.formWidget.action, this.formData);
1155
+ }
1156
+ }
1157
+ else {
1158
+ // Cancel or other actions
1159
+ if (this.widgetAction) {
1160
+ this.widgetAction(this.formWidget.action, { actionType });
1161
+ }
1162
+ }
1163
+ }
1164
+ validateForm() {
1165
+ for (const field of this.formWidget.fields || []) {
1166
+ if (field.required) {
1167
+ const value = this.formData[field.name];
1168
+ if (value === undefined || value === null || value === '' ||
1169
+ (Array.isArray(value) && value.length === 0)) {
1170
+ return true; // Has errors
1171
+ }
1172
+ }
1173
+ }
1174
+ return false; // No errors
1175
+ }
1176
+ gatherFormData() {
1177
+ // Gather data from the rendered field widgets
1178
+ // Since each widget component manages its own state via ngModel,
1179
+ // we need to query the DOM to get the current values
1180
+ const fields = this.formWidget.fields || [];
1181
+ fields.forEach((field) => {
1182
+ const fieldId = this.getFieldId(field.name);
1183
+ const element = document.getElementById(fieldId);
1184
+ if (element) {
1185
+ if (element.type === 'checkbox') {
1186
+ this.formData[field.name] = element.checked;
1187
+ }
1188
+ else if (element.type === 'file') {
1189
+ this.formData[field.name] = element.files?.[0];
1190
+ }
1191
+ else if (element.tagName === 'SELECT' && element.multiple) {
1192
+ const select = element;
1193
+ this.formData[field.name] = Array.from(select.selectedOptions).map(opt => opt.value);
1194
+ }
1195
+ else {
1196
+ this.formData[field.name] = element.value;
1197
+ }
1198
+ }
1199
+ });
1200
+ }
1201
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FormWidgetComponent, deps: [{ token: i0.Injector }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Component });
1202
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: FormWidgetComponent, isStandalone: true, selector: "bbq-form-widget", inputs: { widget: "widget" }, viewQueries: [{ propertyName: "fieldContainers", predicate: ["fieldContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: `
1203
+ <div
1204
+ class="bbq-widget bbq-form"
1205
+ [attr.data-widget-id]="formId"
1206
+ [attr.data-widget-type]="'form'"
1207
+ [attr.data-action]="formWidget.action">
1208
+ <fieldset class="bbq-form-fieldset">
1209
+ <legend class="bbq-form-title">{{ formWidget.title }}</legend>
1210
+
1211
+ @for (field of formWidget.fields; track field.name) {
1212
+ <div
1213
+ class="bbq-form-field"
1214
+ [class.bbq-form-field-required]="field.required"
1215
+ [attr.data-required]="field.required ? 'true' : null">
1216
+ <label class="bbq-form-field-label" [attr.for]="getFieldId(field.name)">
1217
+ {{ field.label }}
1218
+ @if (field.required) {
1219
+ <span class="bbq-form-required">*</span>
1220
+ }
1221
+ </label>
1222
+
1223
+ <div #fieldContainer class="bbq-form-field-widget"></div>
1224
+
1225
+ @if (getFieldProp(field, 'validationHint')) {
1226
+ <span class="bbq-form-field-hint">{{ getFieldProp(field, 'validationHint') }}</span>
1227
+ }
1228
+ </div>
1229
+ }
1230
+
1231
+ <div class="bbq-form-validation-message" [style.display]="showValidationMessage ? 'block' : 'none'">
1232
+ Please fill in all required fields before submitting.
1233
+ </div>
1234
+
1235
+ @if (formWidget.actions && formWidget.actions.length > 0) {
1236
+ <div class="bbq-form-actions">
1237
+ @for (action of formWidget.actions; track action.label) {
1238
+ <button
1239
+ type="button"
1240
+ class="bbq-form-button"
1241
+ [class.bbq-form-submit]="action.type === 'submit'"
1242
+ [class.bbq-form-cancel]="action.type !== 'submit'"
1243
+ [attr.data-action]="formWidget.action"
1244
+ [attr.data-action-type]="action.type"
1245
+ (click)="onActionClick(action.type)">
1246
+ {{ action.label }}
1247
+ </button>
1248
+ }
1249
+ </div>
1250
+ }
1251
+ </fieldset>
1252
+ </div>
1253
+ `, isInline: true, styles: [".bbq-form-field-widget{display:contents}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
1254
+ }
1255
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: FormWidgetComponent, decorators: [{
1256
+ type: Component,
1257
+ args: [{ selector: 'bbq-form-widget', standalone: true, imports: [CommonModule, FormsModule], template: `
1258
+ <div
1259
+ class="bbq-widget bbq-form"
1260
+ [attr.data-widget-id]="formId"
1261
+ [attr.data-widget-type]="'form'"
1262
+ [attr.data-action]="formWidget.action">
1263
+ <fieldset class="bbq-form-fieldset">
1264
+ <legend class="bbq-form-title">{{ formWidget.title }}</legend>
1265
+
1266
+ @for (field of formWidget.fields; track field.name) {
1267
+ <div
1268
+ class="bbq-form-field"
1269
+ [class.bbq-form-field-required]="field.required"
1270
+ [attr.data-required]="field.required ? 'true' : null">
1271
+ <label class="bbq-form-field-label" [attr.for]="getFieldId(field.name)">
1272
+ {{ field.label }}
1273
+ @if (field.required) {
1274
+ <span class="bbq-form-required">*</span>
1275
+ }
1276
+ </label>
1277
+
1278
+ <div #fieldContainer class="bbq-form-field-widget"></div>
1279
+
1280
+ @if (getFieldProp(field, 'validationHint')) {
1281
+ <span class="bbq-form-field-hint">{{ getFieldProp(field, 'validationHint') }}</span>
1282
+ }
1283
+ </div>
1284
+ }
1285
+
1286
+ <div class="bbq-form-validation-message" [style.display]="showValidationMessage ? 'block' : 'none'">
1287
+ Please fill in all required fields before submitting.
1288
+ </div>
1289
+
1290
+ @if (formWidget.actions && formWidget.actions.length > 0) {
1291
+ <div class="bbq-form-actions">
1292
+ @for (action of formWidget.actions; track action.label) {
1293
+ <button
1294
+ type="button"
1295
+ class="bbq-form-button"
1296
+ [class.bbq-form-submit]="action.type === 'submit'"
1297
+ [class.bbq-form-cancel]="action.type !== 'submit'"
1298
+ [attr.data-action]="formWidget.action"
1299
+ [attr.data-action-type]="action.type"
1300
+ (click)="onActionClick(action.type)">
1301
+ {{ action.label }}
1302
+ </button>
1303
+ }
1304
+ </div>
1305
+ }
1306
+ </fieldset>
1307
+ </div>
1308
+ `, styles: [".bbq-form-field-widget{display:contents}\n"] }]
1309
+ }], ctorParameters: () => [{ type: i0.Injector }, { type: i0.EnvironmentInjector }], propDecorators: { widget: [{
1310
+ type: Input
1311
+ }], fieldContainers: [{
1312
+ type: ViewChildren,
1313
+ args: ['fieldContainer', { read: ViewContainerRef }]
1314
+ }] } });
1315
+
1316
+ class ImageWidgetComponent {
1317
+ widget;
1318
+ widgetAction;
1319
+ get imageWidget() {
1320
+ return this.widget;
1321
+ }
1322
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ImageWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1323
+ 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: `
1324
+ <div
1325
+ class="bbq-widget bbq-image"
1326
+ [attr.data-widget-type]="'image'"
1327
+ [attr.data-action]="imageWidget.action">
1328
+ <img
1329
+ class="bbq-image-img"
1330
+ [src]="imageWidget.imageUrl"
1331
+ [alt]="imageWidget.alt || 'Image'"
1332
+ [style.width]="imageWidget.width ? imageWidget.width + 'px' : 'auto'"
1333
+ [style.height]="imageWidget.height ? imageWidget.height + 'px' : 'auto'"
1334
+ loading="lazy" />
1335
+ </div>
1336
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1337
+ }
1338
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ImageWidgetComponent, decorators: [{
1339
+ type: Component,
1340
+ args: [{ selector: 'bbq-image-widget', standalone: true, imports: [CommonModule], template: `
1341
+ <div
1342
+ class="bbq-widget bbq-image"
1343
+ [attr.data-widget-type]="'image'"
1344
+ [attr.data-action]="imageWidget.action">
1345
+ <img
1346
+ class="bbq-image-img"
1347
+ [src]="imageWidget.imageUrl"
1348
+ [alt]="imageWidget.alt || 'Image'"
1349
+ [style.width]="imageWidget.width ? imageWidget.width + 'px' : 'auto'"
1350
+ [style.height]="imageWidget.height ? imageWidget.height + 'px' : 'auto'"
1351
+ loading="lazy" />
1352
+ </div>
1353
+ ` }]
1354
+ }], propDecorators: { widget: [{
1355
+ type: Input
1356
+ }] } });
1357
+
1358
+ class ImageCollectionWidgetComponent {
1359
+ widget;
1360
+ widgetAction;
1361
+ get imageCollectionWidget() {
1362
+ return this.widget;
1363
+ }
1364
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ImageCollectionWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1365
+ 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: `
1366
+ <div
1367
+ class="bbq-widget bbq-image-collection"
1368
+ [attr.data-widget-type]="'imagecollection'"
1369
+ [attr.data-action]="imageCollectionWidget.action">
1370
+ <div class="bbq-image-collection-grid">
1371
+ @for (image of imageCollectionWidget.images; track image.imageUrl) {
1372
+ <div class="bbq-image-collection-item">
1373
+ <img
1374
+ class="bbq-image-collection-img"
1375
+ [src]="image.imageUrl"
1376
+ [alt]="image.alt || 'Image'"
1377
+ [style.width]="image.width ? image.width + 'px' : 'auto'"
1378
+ [style.height]="image.height ? image.height + 'px' : 'auto'"
1379
+ loading="lazy" />
1380
+ </div>
1381
+ }
1382
+ </div>
1383
+ </div>
1384
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1385
+ }
1386
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ImageCollectionWidgetComponent, decorators: [{
1387
+ type: Component,
1388
+ args: [{ selector: 'bbq-imagecollection-widget', standalone: true, imports: [CommonModule], template: `
1389
+ <div
1390
+ class="bbq-widget bbq-image-collection"
1391
+ [attr.data-widget-type]="'imagecollection'"
1392
+ [attr.data-action]="imageCollectionWidget.action">
1393
+ <div class="bbq-image-collection-grid">
1394
+ @for (image of imageCollectionWidget.images; track image.imageUrl) {
1395
+ <div class="bbq-image-collection-item">
1396
+ <img
1397
+ class="bbq-image-collection-img"
1398
+ [src]="image.imageUrl"
1399
+ [alt]="image.alt || 'Image'"
1400
+ [style.width]="image.width ? image.width + 'px' : 'auto'"
1401
+ [style.height]="image.height ? image.height + 'px' : 'auto'"
1402
+ loading="lazy" />
1403
+ </div>
1404
+ }
1405
+ </div>
1406
+ </div>
1407
+ ` }]
1408
+ }], propDecorators: { widget: [{
1409
+ type: Input
1410
+ }] } });
1411
+
1412
+ /**
1413
+ * Built-in widget components
1414
+ * These components provide Angular implementations for all standard widget types
1415
+ */
1416
+
1417
+ /**
1418
+ * Registry of all built-in widget components
1419
+ * Maps widget type to Angular component class
1420
+ */
1421
+ const BUILT_IN_WIDGET_COMPONENTS = {
1422
+ button: ButtonWidgetComponent,
1423
+ card: CardWidgetComponent,
1424
+ input: InputWidgetComponent,
1425
+ textarea: TextAreaWidgetComponent,
1426
+ dropdown: DropdownWidgetComponent,
1427
+ slider: SliderWidgetComponent,
1428
+ toggle: ToggleWidgetComponent,
1429
+ fileupload: FileUploadWidgetComponent,
1430
+ themeswitcher: ThemeSwitcherWidgetComponent,
1431
+ datepicker: DatePickerWidgetComponent,
1432
+ multiselect: MultiSelectWidgetComponent,
1433
+ progressbar: ProgressBarWidgetComponent,
1434
+ form: FormWidgetComponent,
1435
+ image: ImageWidgetComponent,
1436
+ imagecollection: ImageCollectionWidgetComponent,
1437
+ };
1438
+
51
1439
  const WIDGET_EVENT_MANAGER_FACTORY = new InjectionToken('WIDGET_EVENT_MANAGER_FACTORY');
52
1440
  /**
53
1441
  * Injection token for SsrWidgetRenderer
@@ -62,6 +1450,19 @@ const WIDGET_EVENT_MANAGER_FACTORY = new InjectionToken('WIDGET_EVENT_MANAGER_FA
62
1450
  * ```
63
1451
  */
64
1452
  const SSR_WIDGET_RENDERER = new InjectionToken('SSR_WIDGET_RENDERER');
1453
+ /**
1454
+ * Injection token for AngularWidgetRenderer
1455
+ *
1456
+ * Use this token to inject an AngularWidgetRenderer instance in your components.
1457
+ * This is the recommended renderer for Angular applications as it provides
1458
+ * native Angular component rendering instead of HTML string rendering.
1459
+ *
1460
+ * @example
1461
+ * ```typescript
1462
+ * constructor(@Inject(ANGULAR_WIDGET_RENDERER) private renderer: AngularWidgetRenderer) {}
1463
+ * ```
1464
+ */
1465
+ const ANGULAR_WIDGET_RENDERER = new InjectionToken('ANGULAR_WIDGET_RENDERER');
65
1466
  /**
66
1467
  * Factory function for creating WidgetEventManager instances
67
1468
  *
@@ -85,6 +1486,19 @@ function widgetEventManagerFactoryProvider() {
85
1486
  function ssrWidgetRendererFactory() {
86
1487
  return new SsrWidgetRenderer();
87
1488
  }
1489
+ /**
1490
+ * Factory function for creating AngularWidgetRenderer instances
1491
+ *
1492
+ * This factory creates an AngularWidgetRenderer with all built-in widget components
1493
+ * pre-registered. This is the recommended renderer for Angular applications.
1494
+ *
1495
+ * @returns A new AngularWidgetRenderer instance with built-in components registered
1496
+ */
1497
+ function angularWidgetRendererFactory() {
1498
+ const renderer = new AngularWidgetRenderer();
1499
+ renderer.registerBuiltInComponents(BUILT_IN_WIDGET_COMPONENTS);
1500
+ return renderer;
1501
+ }
88
1502
 
89
1503
  /**
90
1504
  * Service for registering custom widget factories and renderers
@@ -228,6 +1642,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
228
1642
  */
229
1643
  class WidgetRendererComponent {
230
1644
  renderer;
1645
+ angularRenderer;
231
1646
  eventManagerFactory;
232
1647
  widgetRegistry;
233
1648
  injector;
@@ -246,8 +1661,9 @@ class WidgetRendererComponent {
246
1661
  isViewInitialized = false;
247
1662
  dynamicComponents = [];
248
1663
  dynamicViews = [];
249
- constructor(renderer, eventManagerFactory, widgetRegistry, injector, environmentInjector) {
1664
+ constructor(renderer, angularRenderer, eventManagerFactory, widgetRegistry, injector, environmentInjector) {
250
1665
  this.renderer = renderer;
1666
+ this.angularRenderer = angularRenderer;
251
1667
  this.eventManagerFactory = eventManagerFactory;
252
1668
  this.widgetRegistry = widgetRegistry;
253
1669
  this.injector = injector;
@@ -312,7 +1728,18 @@ class WidgetRendererComponent {
312
1728
  html: customRenderer(widget),
313
1729
  };
314
1730
  }
315
- // Default: render using the BbQ library renderer
1731
+ // Try to use AngularWidgetRenderer for built-in widgets
1732
+ if (this.angularRenderer) {
1733
+ const componentType = this.angularRenderer.getComponentType(widget);
1734
+ if (componentType) {
1735
+ return {
1736
+ index,
1737
+ widget,
1738
+ isHtml: false,
1739
+ };
1740
+ }
1741
+ }
1742
+ // Fallback: render using the SSR library renderer
316
1743
  return {
317
1744
  index,
318
1745
  widget,
@@ -347,18 +1774,26 @@ class WidgetRendererComponent {
347
1774
  this.widgetItems.forEach((item) => {
348
1775
  if (!item.isHtml) {
349
1776
  const customRenderer = this.widgetRegistry.getRenderer(item.widget.type);
350
- if (!customRenderer)
351
- return;
352
1777
  const targetDiv = dynamicWidgetDivs[dynamicIndex];
353
1778
  if (!targetDiv)
354
1779
  return;
355
1780
  // Clear the div content before rendering
356
1781
  targetDiv.innerHTML = '';
357
- if (isComponentRenderer(customRenderer)) {
358
- this.renderComponent(customRenderer, item.widget, targetDiv);
1782
+ // Handle custom renderers first
1783
+ if (customRenderer) {
1784
+ if (isComponentRenderer(customRenderer)) {
1785
+ this.renderComponent(customRenderer, item.widget, targetDiv);
1786
+ }
1787
+ else if (isTemplateRenderer(customRenderer)) {
1788
+ this.renderTemplate(customRenderer, item.widget, targetDiv);
1789
+ }
359
1790
  }
360
- else if (isTemplateRenderer(customRenderer)) {
361
- this.renderTemplate(customRenderer, item.widget, targetDiv);
1791
+ else if (this.angularRenderer) {
1792
+ // Try to render using AngularWidgetRenderer for built-in widgets
1793
+ const componentType = this.angularRenderer.getComponentType(item.widget);
1794
+ if (componentType) {
1795
+ this.renderComponent(componentType, item.widget, targetDiv);
1796
+ }
362
1797
  }
363
1798
  dynamicIndex++;
364
1799
  }
@@ -382,11 +1817,15 @@ class WidgetRendererComponent {
382
1817
  // Safely set component inputs if they exist
383
1818
  const instance = componentRef.instance;
384
1819
  // Set widget property if it exists in the prototype chain
385
- instance['widget'] = widget;
1820
+ if (!instance['widget']) {
1821
+ instance['widget'] = widget;
1822
+ }
386
1823
  // Set widgetAction property if it exists in the prototype chain
387
- instance['widgetAction'] = (actionName, payload) => {
388
- this.widgetAction.emit({ actionName, payload });
389
- };
1824
+ if (!instance['widgetAction']) {
1825
+ instance['widgetAction'] = (actionName, payload) => {
1826
+ this.widgetAction.emit({ actionName, payload });
1827
+ };
1828
+ }
390
1829
  // Attach the component's host view to the target element
391
1830
  targetElement.appendChild(componentRef.location.nativeElement);
392
1831
  // Store reference for cleanup
@@ -472,10 +1911,11 @@ class WidgetRendererComponent {
472
1911
  // Cleanup event manager
473
1912
  this.eventManager = undefined;
474
1913
  }
475
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: WidgetRendererComponent, deps: [{ token: SSR_WIDGET_RENDERER }, { token: WIDGET_EVENT_MANAGER_FACTORY }, { token: WidgetRegistryService }, { token: i0.Injector }, { token: i0.EnvironmentInjector }], target: i0.ɵɵFactoryTarget.Component });
1914
+ 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 });
476
1915
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: WidgetRendererComponent, isStandalone: true, selector: "bbq-widget-renderer", inputs: { widgets: "widgets" }, outputs: { widgetAction: "widgetAction" }, providers: [
477
1916
  { provide: WIDGET_EVENT_MANAGER_FACTORY, useFactory: widgetEventManagerFactoryProvider },
478
1917
  { provide: SSR_WIDGET_RENDERER, useFactory: ssrWidgetRendererFactory },
1918
+ { provide: ANGULAR_WIDGET_RENDERER, useFactory: angularWidgetRendererFactory },
479
1919
  ], viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["widgetContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
480
1920
  <div #widgetContainer class="bbq-widgets-container" (click)="handleClick($event)">
481
1921
  @for (item of widgetItems; track item.index) {
@@ -493,6 +1933,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
493
1933
  args: [{ selector: 'bbq-widget-renderer', standalone: true, imports: [CommonModule], providers: [
494
1934
  { provide: WIDGET_EVENT_MANAGER_FACTORY, useFactory: widgetEventManagerFactoryProvider },
495
1935
  { provide: SSR_WIDGET_RENDERER, useFactory: ssrWidgetRendererFactory },
1936
+ { provide: ANGULAR_WIDGET_RENDERER, useFactory: angularWidgetRendererFactory },
496
1937
  ], template: `
497
1938
  <div #widgetContainer class="bbq-widgets-container" (click)="handleClick($event)">
498
1939
  @for (item of widgetItems; track item.index) {
@@ -504,9 +1945,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
504
1945
  }
505
1946
  </div>
506
1947
  `, styles: [".bbq-widgets-container{margin-top:.5rem}.bbq-widget{margin-bottom:.5rem}\n"] }]
507
- }], ctorParameters: () => [{ type: i2.SsrWidgetRenderer, decorators: [{
1948
+ }], ctorParameters: () => [{ type: i2$1.SsrWidgetRenderer, decorators: [{
508
1949
  type: Inject,
509
1950
  args: [SSR_WIDGET_RENDERER]
1951
+ }] }, { type: AngularWidgetRenderer, decorators: [{
1952
+ type: Optional
1953
+ }, {
1954
+ type: Inject,
1955
+ args: [ANGULAR_WIDGET_RENDERER]
510
1956
  }] }, { type: undefined, decorators: [{
511
1957
  type: Inject,
512
1958
  args: [WIDGET_EVENT_MANAGER_FACTORY]
@@ -519,6 +1965,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
519
1965
  args: ['widgetContainer', { static: false }]
520
1966
  }] } });
521
1967
 
1968
+ /**
1969
+ * Angular widget renderers
1970
+ */
1971
+
522
1972
  /**
523
1973
  * @bbq-chat/widgets-angular
524
1974
  *
@@ -532,11 +1982,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImpor
532
1982
  */
533
1983
  // Export components
534
1984
  // Version
535
- const VERSION = '1.0.3';
1985
+ const VERSION = '1.0.4';
536
1986
 
537
1987
  /**
538
1988
  * Generated bundle index. Do not edit.
539
1989
  */
540
1990
 
541
- export { SSR_WIDGET_RENDERER, VERSION, WIDGET_EVENT_MANAGER_FACTORY, WidgetRegistryService, WidgetRendererComponent, isComponentRenderer, isHtmlRenderer, isTemplateRenderer, ssrWidgetRendererFactory, widgetEventManagerFactoryProvider };
1991
+ export { ANGULAR_WIDGET_RENDERER, AngularWidgetRenderer, BUILT_IN_WIDGET_COMPONENTS, ButtonWidgetComponent, CardWidgetComponent, DatePickerWidgetComponent, DropdownWidgetComponent, FileUploadWidgetComponent, 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 };
542
1992
  //# sourceMappingURL=index.mjs.map