@bbq-chat/widgets-angular 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/index.mjs +2152 -0
- package/fesm2022/index.mjs.map +1 -0
- package/package.json +15 -20
- package/types/index.d.ts +660 -0
- package/.angular/cache/21.0.5/ng-packagr/97cbacd0e5e4cb18d1fead4d7f3aee1c3863ba3ffbe7cb7dd7780f237a848a5c +0 -1
- package/.angular/cache/21.0.5/ng-packagr/tsbuildinfo/index.tsbuildinfo +0 -1
- package/.eslintrc.json +0 -23
- package/.prettierrc.json +0 -8
- package/EXAMPLES.md +0 -484
- package/angular.json +0 -36
- package/ng-package.json +0 -9
- package/src/angular-widget-renderer.spec.ts +0 -157
- package/src/components/button.component.ts +0 -35
- package/src/components/card.component.ts +0 -52
- package/src/components/datepicker.component.ts +0 -63
- package/src/components/dropdown.component.ts +0 -65
- package/src/components/fileupload.component.ts +0 -71
- package/src/components/form.component.ts +0 -433
- package/src/components/image.component.ts +0 -33
- package/src/components/imagecollection.component.ts +0 -39
- package/src/components/index.ts +0 -20
- package/src/components/input.component.ts +0 -63
- package/src/components/multiselect.component.ts +0 -67
- package/src/components/progressbar.component.ts +0 -50
- package/src/components/slider.component.ts +0 -67
- package/src/components/textarea.component.ts +0 -63
- package/src/components/themeswitcher.component.ts +0 -46
- package/src/components/toggle.component.ts +0 -63
- package/src/custom-widget-renderer.types.ts +0 -120
- package/src/examples/form-validation-listener.component.ts +0 -41
- package/src/public_api.ts +0 -107
- package/src/renderers/AngularWidgetRenderer.ts +0 -100
- package/src/renderers/built-in-components.ts +0 -41
- package/src/renderers/index.ts +0 -7
- package/src/services/form-validation.service.ts +0 -21
- package/src/widget-di.tokens.ts +0 -95
- package/src/widget-registry.service.ts +0 -128
- package/src/widget-renderer.component.ts +0 -421
- package/tsconfig.json +0 -37
- package/tsconfig.lib.json +0 -18
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -13
|
@@ -1,433 +0,0 @@
|
|
|
1
|
-
import { Component, Input, Output, EventEmitter, OnInit, AfterViewInit, ViewChildren, QueryList, ViewContainerRef, ComponentRef, Injector, EnvironmentInjector, createComponent, Type, OnDestroy, ElementRef } from '@angular/core';
|
|
2
|
-
import { FormValidationService } from '../services/form-validation.service';
|
|
3
|
-
import { CommonModule } from '@angular/common';
|
|
4
|
-
import { FormsModule } from '@angular/forms';
|
|
5
|
-
import type { FormWidget, ChatWidget } from '@bbq-chat/widgets';
|
|
6
|
-
import { CustomWidgetComponent } from '../custom-widget-renderer.types';
|
|
7
|
-
import { InputWidgetComponent } from './input.component';
|
|
8
|
-
import { TextAreaWidgetComponent } from './textarea.component';
|
|
9
|
-
import { DropdownWidgetComponent } from './dropdown.component';
|
|
10
|
-
import { SliderWidgetComponent } from './slider.component';
|
|
11
|
-
import { ToggleWidgetComponent } from './toggle.component';
|
|
12
|
-
import { DatePickerWidgetComponent } from './datepicker.component';
|
|
13
|
-
import { MultiSelectWidgetComponent } from './multiselect.component';
|
|
14
|
-
import { FileUploadWidgetComponent } from './fileupload.component';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Helper class to wrap form fields as widgets for dynamic rendering
|
|
18
|
-
*/
|
|
19
|
-
class FormFieldWidget implements ChatWidget {
|
|
20
|
-
readonly type: string;
|
|
21
|
-
readonly label: string;
|
|
22
|
-
readonly action: string;
|
|
23
|
-
readonly appearance = 'form';
|
|
24
|
-
readonly hideLabel = true;
|
|
25
|
-
|
|
26
|
-
constructor(
|
|
27
|
-
public field: any,
|
|
28
|
-
public formId: string
|
|
29
|
-
) {
|
|
30
|
-
this.type = this.mapFieldTypeToWidgetType(field.type);
|
|
31
|
-
this.label = field.label;
|
|
32
|
-
this.action = `${formId}_${field.name}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private mapFieldTypeToWidgetType(fieldType: string): string {
|
|
36
|
-
const typeMap: Record<string, string> = {
|
|
37
|
-
'input': 'input',
|
|
38
|
-
'text': 'input',
|
|
39
|
-
'email': 'input',
|
|
40
|
-
'number': 'input',
|
|
41
|
-
'password': 'input',
|
|
42
|
-
'textarea': 'textarea',
|
|
43
|
-
'dropdown': 'dropdown',
|
|
44
|
-
'select': 'dropdown',
|
|
45
|
-
'slider': 'slider',
|
|
46
|
-
'toggle': 'toggle',
|
|
47
|
-
'datepicker': 'datepicker',
|
|
48
|
-
'multiselect': 'multiselect',
|
|
49
|
-
'fileupload': 'fileupload'
|
|
50
|
-
};
|
|
51
|
-
return typeMap[fieldType] || 'input';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Map field properties to widget properties
|
|
55
|
-
get placeholder(): string | undefined {
|
|
56
|
-
return this.field.placeholder ?? undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
get maxLength(): number | undefined {
|
|
60
|
-
return this.field['maxLength'];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
get rows(): number | undefined {
|
|
64
|
-
return this.field['rows'];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
get options(): string[] {
|
|
68
|
-
return this.field['options'] || [];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
get min(): number {
|
|
72
|
-
return this.field['min'] ?? 0;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
get max(): number {
|
|
76
|
-
return this.field['max'] ?? 100;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
get step(): number {
|
|
80
|
-
return this.field['step'] ?? 1;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
get defaultValue(): any {
|
|
84
|
-
return this.field['defaultValue'] ?? (this.type === 'slider' ? this.min : undefined);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
get minDate(): string | undefined {
|
|
88
|
-
return this.field['minDate'];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
get maxDate(): string | undefined {
|
|
92
|
-
return this.field['maxDate'];
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
get accept(): string | undefined {
|
|
96
|
-
return this.field['accept'];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
get maxBytes(): number | undefined {
|
|
100
|
-
return this.field['maxBytes'];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ChatWidget interface methods
|
|
104
|
-
toJson(): string {
|
|
105
|
-
return JSON.stringify(this.toObject());
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
toObject(): any {
|
|
109
|
-
return {
|
|
110
|
-
type: this.type,
|
|
111
|
-
label: this.label,
|
|
112
|
-
action: this.action,
|
|
113
|
-
...this.field
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
@Component({
|
|
119
|
-
selector: 'bbq-form-widget',
|
|
120
|
-
standalone: true,
|
|
121
|
-
imports: [CommonModule, FormsModule],
|
|
122
|
-
template: `
|
|
123
|
-
<div
|
|
124
|
-
class="bbq-widget bbq-form"
|
|
125
|
-
[class.bbq-form-submitted]="isSubmitted"
|
|
126
|
-
[attr.aria-disabled]="isSubmitted ? 'true' : null"
|
|
127
|
-
[attr.data-widget-id]="formId"
|
|
128
|
-
[attr.data-widget-type]="'form'"
|
|
129
|
-
[attr.data-action]="formWidget.action">
|
|
130
|
-
<fieldset class="bbq-form-fieldset">
|
|
131
|
-
<legend class="bbq-form-title">{{ formWidget.title }}</legend>
|
|
132
|
-
|
|
133
|
-
@for (field of formWidget.fields; track field.name) {
|
|
134
|
-
<div
|
|
135
|
-
class="bbq-form-field"
|
|
136
|
-
[class.bbq-form-field-required]="field.required"
|
|
137
|
-
[attr.data-required]="field.required ? 'true' : null">
|
|
138
|
-
<label class="bbq-form-field-label" [attr.for]="getFieldId(field.name)">
|
|
139
|
-
{{ field.label }}
|
|
140
|
-
@if (field.required) {
|
|
141
|
-
<span class="bbq-form-required">*</span>
|
|
142
|
-
}
|
|
143
|
-
</label>
|
|
144
|
-
|
|
145
|
-
<div #fieldContainer class="bbq-form-field-widget"></div>
|
|
146
|
-
|
|
147
|
-
@if (getFieldProp(field, 'validationHint')) {
|
|
148
|
-
<span class="bbq-form-field-hint">{{ getFieldProp(field, 'validationHint') }}</span>
|
|
149
|
-
}
|
|
150
|
-
</div>
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
<div class="bbq-form-validation-message" [style.display]="showValidationMessage ? 'block' : 'none'">
|
|
154
|
-
Please fill in all required fields before submitting.
|
|
155
|
-
</div>
|
|
156
|
-
|
|
157
|
-
@if (formWidget.actions && formWidget.actions.length > 0) {
|
|
158
|
-
<div class="bbq-form-actions">
|
|
159
|
-
@for (action of formWidget.actions; track action.label) {
|
|
160
|
-
<button
|
|
161
|
-
type="button"
|
|
162
|
-
class="bbq-form-button"
|
|
163
|
-
[class.bbq-form-submit]="action.type === 'submit'"
|
|
164
|
-
[class.bbq-form-cancel]="action.type !== 'submit'"
|
|
165
|
-
[attr.data-action]="formWidget.action"
|
|
166
|
-
[attr.data-action-type]="action.type"
|
|
167
|
-
[disabled]="isSubmitted"
|
|
168
|
-
(click)="onActionClick(action.type)">
|
|
169
|
-
{{ action.label }}
|
|
170
|
-
</button>
|
|
171
|
-
}
|
|
172
|
-
</div>
|
|
173
|
-
}
|
|
174
|
-
</fieldset>
|
|
175
|
-
</div>
|
|
176
|
-
`,
|
|
177
|
-
styles: [`
|
|
178
|
-
.bbq-form-field-widget {
|
|
179
|
-
display: contents;
|
|
180
|
-
}
|
|
181
|
-
.bbq-form-submitted {
|
|
182
|
-
opacity: 0.7;
|
|
183
|
-
}
|
|
184
|
-
`]
|
|
185
|
-
})
|
|
186
|
-
export class FormWidgetComponent implements CustomWidgetComponent, OnInit, AfterViewInit, OnDestroy {
|
|
187
|
-
@Input() widget!: any;
|
|
188
|
-
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
189
|
-
@Input() fieldComponentRegistryOverride?: Record<string, Type<CustomWidgetComponent>>;
|
|
190
|
-
@Output() validationState = new EventEmitter<{ valid: boolean; errors: Array<{ field: string; reason?: string }> }>();
|
|
191
|
-
|
|
192
|
-
@ViewChildren('fieldContainer', { read: ViewContainerRef })
|
|
193
|
-
fieldContainers!: QueryList<ViewContainerRef>;
|
|
194
|
-
|
|
195
|
-
formId = '';
|
|
196
|
-
formData: Record<string, any> = {};
|
|
197
|
-
showValidationMessage = false;
|
|
198
|
-
private componentRefs: ComponentRef<any>[] = [];
|
|
199
|
-
|
|
200
|
-
// Component registry for field types (can be extended via `fieldComponentRegistryOverride`)
|
|
201
|
-
private fieldComponentRegistry: Record<string, Type<CustomWidgetComponent>> = {
|
|
202
|
-
'input': InputWidgetComponent,
|
|
203
|
-
'text': InputWidgetComponent,
|
|
204
|
-
'email': InputWidgetComponent,
|
|
205
|
-
'number': InputWidgetComponent,
|
|
206
|
-
'password': InputWidgetComponent,
|
|
207
|
-
'textarea': TextAreaWidgetComponent,
|
|
208
|
-
'dropdown': DropdownWidgetComponent,
|
|
209
|
-
'select': DropdownWidgetComponent,
|
|
210
|
-
'slider': SliderWidgetComponent,
|
|
211
|
-
'toggle': ToggleWidgetComponent,
|
|
212
|
-
'datepicker': DatePickerWidgetComponent,
|
|
213
|
-
'multiselect': MultiSelectWidgetComponent,
|
|
214
|
-
'fileupload': FileUploadWidgetComponent,
|
|
215
|
-
'checkbox': ToggleWidgetComponent,
|
|
216
|
-
'radio': ToggleWidgetComponent,
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// Whether the form has been submitted; when true, user interaction is disabled
|
|
220
|
-
isSubmitted = false;
|
|
221
|
-
|
|
222
|
-
get formWidget(): FormWidget {
|
|
223
|
-
return this.widget as FormWidget;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
constructor(
|
|
227
|
-
private injector: Injector,
|
|
228
|
-
private environmentInjector: EnvironmentInjector
|
|
229
|
-
, private hostRef: ElementRef
|
|
230
|
-
, private formValidationService: FormValidationService
|
|
231
|
-
) {}
|
|
232
|
-
|
|
233
|
-
ngOnInit() {
|
|
234
|
-
this.formId = `bbq-${this.formWidget.action.replace(/\s+/g, '-').toLowerCase()}`;
|
|
235
|
-
|
|
236
|
-
// Initialize form data with default values
|
|
237
|
-
for (const field of this.formWidget.fields || []) {
|
|
238
|
-
if (field.type === 'slider') {
|
|
239
|
-
this.formData[field.name] = field['default'] ?? field['defaultValue'] ?? field['min'] ?? 0;
|
|
240
|
-
} else if (field.type === 'toggle' || field.type === 'checkbox') {
|
|
241
|
-
this.formData[field.name] = field['defaultValue'] ?? false;
|
|
242
|
-
} else if (field.type === 'multiselect') {
|
|
243
|
-
this.formData[field.name] = [];
|
|
244
|
-
} else {
|
|
245
|
-
this.formData[field.name] = '';
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Merge any overrides provided by the consumer
|
|
250
|
-
if (this.fieldComponentRegistryOverride) {
|
|
251
|
-
this.fieldComponentRegistry = { ...this.fieldComponentRegistry, ...this.fieldComponentRegistryOverride };
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
ngAfterViewInit() {
|
|
256
|
-
// Render field widgets dynamically
|
|
257
|
-
setTimeout(() => this.renderFieldWidgets(), 0);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
ngOnDestroy() {
|
|
261
|
-
// Clean up component refs
|
|
262
|
-
this.componentRefs.forEach(ref => ref.destroy());
|
|
263
|
-
this.componentRefs = [];
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
private renderFieldWidgets() {
|
|
267
|
-
const containers = this.fieldContainers.toArray();
|
|
268
|
-
const fields = this.formWidget.fields || [];
|
|
269
|
-
|
|
270
|
-
fields.forEach((field: any, index: number) => {
|
|
271
|
-
const container = containers[index];
|
|
272
|
-
if (!container) return;
|
|
273
|
-
|
|
274
|
-
const componentType = this.fieldComponentRegistry[field.type];
|
|
275
|
-
if (!componentType) {
|
|
276
|
-
// Fallback to input for unknown types
|
|
277
|
-
this.renderInputFallback(container, field);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Create the field widget
|
|
282
|
-
const fieldWidget = new FormFieldWidget(field, this.formId);
|
|
283
|
-
|
|
284
|
-
// Create the component
|
|
285
|
-
const componentRef = createComponent(componentType, {
|
|
286
|
-
environmentInjector: this.environmentInjector,
|
|
287
|
-
elementInjector: this.injector,
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// Set component inputs
|
|
291
|
-
const instance = componentRef.instance as any;
|
|
292
|
-
instance['widget'] = fieldWidget;
|
|
293
|
-
// Pass current disabled state so custom components can opt-in to being readonly
|
|
294
|
-
instance['disabled'] = this.isSubmitted;
|
|
295
|
-
|
|
296
|
-
// Connect to form data via widgetAction
|
|
297
|
-
instance['widgetAction'] = (actionName: string, payload: unknown) => {
|
|
298
|
-
// Handle field value changes - for now, we'll sync via the rendered widget's internal state
|
|
299
|
-
// The actual form submission will gather values from the DOM
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
// Attach to container
|
|
303
|
-
container.insert(componentRef.hostView);
|
|
304
|
-
this.componentRefs.push(componentRef);
|
|
305
|
-
|
|
306
|
-
// Trigger change detection
|
|
307
|
-
componentRef.changeDetectorRef.detectChanges();
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
private renderInputFallback(container: ViewContainerRef, field: any) {
|
|
312
|
-
// For unsupported field types, render a basic input
|
|
313
|
-
const fieldWidget = new FormFieldWidget(field, this.formId);
|
|
314
|
-
const componentRef = createComponent(InputWidgetComponent, {
|
|
315
|
-
environmentInjector: this.environmentInjector,
|
|
316
|
-
elementInjector: this.injector,
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
const instance = componentRef.instance as any;
|
|
320
|
-
instance['widget'] = fieldWidget;
|
|
321
|
-
instance['disabled'] = this.isSubmitted;
|
|
322
|
-
|
|
323
|
-
container.insert(componentRef.hostView);
|
|
324
|
-
this.componentRefs.push(componentRef);
|
|
325
|
-
componentRef.changeDetectorRef.detectChanges();
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
getFieldId(fieldName: string): string {
|
|
329
|
-
// Match the ID format used by dynamically rendered input widgets
|
|
330
|
-
return `bbq-${this.formId}_${fieldName}-input`;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
getFieldProp(field: any, prop: string): any {
|
|
334
|
-
return field[prop];
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
onActionClick(actionType: string) {
|
|
338
|
-
if (this.isSubmitted) return;
|
|
339
|
-
if (actionType === 'submit') {
|
|
340
|
-
// Validate required fields
|
|
341
|
-
const hasErrors = this.validateForm();
|
|
342
|
-
|
|
343
|
-
if (hasErrors) {
|
|
344
|
-
this.showValidationMessage = true;
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
this.showValidationMessage = false;
|
|
349
|
-
|
|
350
|
-
// Mark submitted to prevent further interaction
|
|
351
|
-
this.isSubmitted = true;
|
|
352
|
-
// Inform child components and disable DOM controls
|
|
353
|
-
this.componentRefs.forEach(ref => {
|
|
354
|
-
try {
|
|
355
|
-
(ref.instance as any)['disabled'] = true;
|
|
356
|
-
ref.changeDetectorRef.detectChanges();
|
|
357
|
-
} catch { }
|
|
358
|
-
});
|
|
359
|
-
this.disableFormInteraction();
|
|
360
|
-
|
|
361
|
-
// Gather form data from the DOM (since widgets manage their own state)
|
|
362
|
-
this.gatherFormData();
|
|
363
|
-
|
|
364
|
-
if (this.widgetAction) {
|
|
365
|
-
this.widgetAction(this.formWidget.action, this.formData);
|
|
366
|
-
}
|
|
367
|
-
} else {
|
|
368
|
-
// Cancel or other actions
|
|
369
|
-
if (this.widgetAction) {
|
|
370
|
-
this.widgetAction(this.formWidget.action, { actionType });
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
private validateForm(): boolean {
|
|
376
|
-
const errors: Array<{ field: string; reason?: string }> = [];
|
|
377
|
-
for (const field of this.formWidget.fields || []) {
|
|
378
|
-
if (field.required) {
|
|
379
|
-
const value = this.formData[field.name];
|
|
380
|
-
if (value === undefined || value === null || value === '' ||
|
|
381
|
-
(Array.isArray(value) && value.length === 0)) {
|
|
382
|
-
errors.push({ field: field.name, reason: 'required' });
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const hasErrors = errors.length > 0;
|
|
388
|
-
const payload = { formId: this.formId, valid: !hasErrors, errors };
|
|
389
|
-
// Emit to the local Output for direct consumers
|
|
390
|
-
this.validationState.emit({ valid: !hasErrors, errors });
|
|
391
|
-
// Also publish via the shared service so consumers that don't have direct access
|
|
392
|
-
// to the component instance can subscribe app-wide.
|
|
393
|
-
try { this.formValidationService.emit(payload); } catch { }
|
|
394
|
-
return hasErrors; // true when there are errors
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
private disableFormInteraction() {
|
|
398
|
-
try {
|
|
399
|
-
const root: HTMLElement = this.hostRef?.nativeElement;
|
|
400
|
-
if (!root) return;
|
|
401
|
-
const controls = root.querySelectorAll('input,select,textarea,button');
|
|
402
|
-
controls.forEach((el: Element) => {
|
|
403
|
-
try { (el as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement).disabled = true; } catch {}
|
|
404
|
-
});
|
|
405
|
-
root.setAttribute('aria-disabled', 'true');
|
|
406
|
-
} catch { }
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
private gatherFormData() {
|
|
410
|
-
// Gather data from the rendered field widgets
|
|
411
|
-
// Since each widget component manages its own state via ngModel,
|
|
412
|
-
// we need to query the DOM to get the current values
|
|
413
|
-
const fields = this.formWidget.fields || [];
|
|
414
|
-
|
|
415
|
-
fields.forEach((field: any) => {
|
|
416
|
-
const fieldId = this.getFieldId(field.name);
|
|
417
|
-
const element = document.getElementById(fieldId) as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
418
|
-
|
|
419
|
-
if (element) {
|
|
420
|
-
if (element.type === 'checkbox') {
|
|
421
|
-
this.formData[field.name] = (element as HTMLInputElement).checked;
|
|
422
|
-
} else if (element.type === 'file') {
|
|
423
|
-
this.formData[field.name] = (element as HTMLInputElement).files?.[0];
|
|
424
|
-
} else if (element.tagName === 'SELECT' && (element as HTMLSelectElement).multiple) {
|
|
425
|
-
const select = element as HTMLSelectElement;
|
|
426
|
-
this.formData[field.name] = Array.from(select.selectedOptions).map(opt => opt.value);
|
|
427
|
-
} else {
|
|
428
|
-
this.formData[field.name] = element.value;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { Component, Input } from '@angular/core';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
|
-
import type { ImageWidget } from '@bbq-chat/widgets';
|
|
4
|
-
import { CustomWidgetComponent } from '../custom-widget-renderer.types';
|
|
5
|
-
|
|
6
|
-
@Component({
|
|
7
|
-
selector: 'bbq-image-widget',
|
|
8
|
-
standalone: true,
|
|
9
|
-
imports: [CommonModule],
|
|
10
|
-
template: `
|
|
11
|
-
<div
|
|
12
|
-
class="bbq-widget bbq-image"
|
|
13
|
-
[attr.data-widget-type]="'image'"
|
|
14
|
-
[attr.data-action]="imageWidget.action">
|
|
15
|
-
<img
|
|
16
|
-
class="bbq-image-img"
|
|
17
|
-
[src]="imageWidget.imageUrl"
|
|
18
|
-
[alt]="imageWidget.alt || 'Image'"
|
|
19
|
-
[style.width]="imageWidget.width ? imageWidget.width + 'px' : 'auto'"
|
|
20
|
-
[style.height]="imageWidget.height ? imageWidget.height + 'px' : 'auto'"
|
|
21
|
-
loading="lazy" />
|
|
22
|
-
</div>
|
|
23
|
-
`,
|
|
24
|
-
styles: []
|
|
25
|
-
})
|
|
26
|
-
export class ImageWidgetComponent implements CustomWidgetComponent {
|
|
27
|
-
@Input() widget!: any;
|
|
28
|
-
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
29
|
-
|
|
30
|
-
get imageWidget(): ImageWidget {
|
|
31
|
-
return this.widget as ImageWidget;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { Component, Input } from '@angular/core';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
|
-
import type { ImageCollectionWidget } from '@bbq-chat/widgets';
|
|
4
|
-
import { CustomWidgetComponent } from '../custom-widget-renderer.types';
|
|
5
|
-
|
|
6
|
-
@Component({
|
|
7
|
-
selector: 'bbq-imagecollection-widget',
|
|
8
|
-
standalone: true,
|
|
9
|
-
imports: [CommonModule],
|
|
10
|
-
template: `
|
|
11
|
-
<div
|
|
12
|
-
class="bbq-widget bbq-image-collection"
|
|
13
|
-
[attr.data-widget-type]="'imagecollection'"
|
|
14
|
-
[attr.data-action]="imageCollectionWidget.action">
|
|
15
|
-
<div class="bbq-image-collection-grid">
|
|
16
|
-
@for (image of imageCollectionWidget.images; track image.imageUrl) {
|
|
17
|
-
<div class="bbq-image-collection-item">
|
|
18
|
-
<img
|
|
19
|
-
class="bbq-image-collection-img"
|
|
20
|
-
[src]="image.imageUrl"
|
|
21
|
-
[alt]="image.alt || 'Image'"
|
|
22
|
-
[style.width]="image.width ? image.width + 'px' : 'auto'"
|
|
23
|
-
[style.height]="image.height ? image.height + 'px' : 'auto'"
|
|
24
|
-
loading="lazy" />
|
|
25
|
-
</div>
|
|
26
|
-
}
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
`,
|
|
30
|
-
styles: []
|
|
31
|
-
})
|
|
32
|
-
export class ImageCollectionWidgetComponent implements CustomWidgetComponent {
|
|
33
|
-
@Input() widget!: any;
|
|
34
|
-
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
35
|
-
|
|
36
|
-
get imageCollectionWidget(): ImageCollectionWidget {
|
|
37
|
-
return this.widget as ImageCollectionWidget;
|
|
38
|
-
}
|
|
39
|
-
}
|
package/src/components/index.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Built-in widget components
|
|
3
|
-
* These components provide Angular implementations for all standard widget types
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export { ButtonWidgetComponent } from './button.component';
|
|
7
|
-
export { CardWidgetComponent } from './card.component';
|
|
8
|
-
export { InputWidgetComponent } from './input.component';
|
|
9
|
-
export { TextAreaWidgetComponent } from './textarea.component';
|
|
10
|
-
export { DropdownWidgetComponent } from './dropdown.component';
|
|
11
|
-
export { SliderWidgetComponent } from './slider.component';
|
|
12
|
-
export { ToggleWidgetComponent } from './toggle.component';
|
|
13
|
-
export { FileUploadWidgetComponent } from './fileupload.component';
|
|
14
|
-
export { ThemeSwitcherWidgetComponent } from './themeswitcher.component';
|
|
15
|
-
export { DatePickerWidgetComponent } from './datepicker.component';
|
|
16
|
-
export { MultiSelectWidgetComponent } from './multiselect.component';
|
|
17
|
-
export { ProgressBarWidgetComponent } from './progressbar.component';
|
|
18
|
-
export { FormWidgetComponent } from './form.component';
|
|
19
|
-
export { ImageWidgetComponent } from './image.component';
|
|
20
|
-
export { ImageCollectionWidgetComponent } from './imagecollection.component';
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { Component, Input, OnInit } from '@angular/core';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
|
-
import { FormsModule } from '@angular/forms';
|
|
4
|
-
import type { InputWidget } from '@bbq-chat/widgets';
|
|
5
|
-
import { CustomWidgetComponent } from '../custom-widget-renderer.types';
|
|
6
|
-
|
|
7
|
-
@Component({
|
|
8
|
-
selector: 'bbq-input-widget',
|
|
9
|
-
standalone: true,
|
|
10
|
-
imports: [CommonModule, FormsModule],
|
|
11
|
-
template: `
|
|
12
|
-
<div
|
|
13
|
-
class="bbq-widget bbq-input"
|
|
14
|
-
[attr.data-widget-type]="'input'">
|
|
15
|
-
<label *ngIf="showLabel" class="bbq-input-label" [attr.for]="inputId">
|
|
16
|
-
{{ inputWidget.label }}
|
|
17
|
-
</label>
|
|
18
|
-
<input
|
|
19
|
-
type="text"
|
|
20
|
-
[id]="inputId"
|
|
21
|
-
[ngClass]="inputClasses"
|
|
22
|
-
[attr.data-action]="inputWidget.action"
|
|
23
|
-
[placeholder]="inputWidget.placeholder || ''"
|
|
24
|
-
[maxLength]="inputWidget.maxLength || 0"
|
|
25
|
-
[(ngModel)]="value" />
|
|
26
|
-
</div>
|
|
27
|
-
`,
|
|
28
|
-
styles: []
|
|
29
|
-
})
|
|
30
|
-
export class InputWidgetComponent implements CustomWidgetComponent, OnInit {
|
|
31
|
-
@Input() widget!: any;
|
|
32
|
-
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
33
|
-
|
|
34
|
-
value = '';
|
|
35
|
-
inputId = '';
|
|
36
|
-
|
|
37
|
-
get inputWidget(): InputWidget {
|
|
38
|
-
return this.widget as InputWidget;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
get showLabel(): boolean {
|
|
42
|
-
const widget = this.inputWidget as any;
|
|
43
|
-
if (widget.hideLabel === true) {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
if (widget.showLabel === false) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
get inputClasses(): string[] {
|
|
53
|
-
return this.isFormAppearance ? ['bbq-form-input'] : ['bbq-input'];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
private get isFormAppearance(): boolean {
|
|
57
|
-
return (this.inputWidget as any).appearance === 'form';
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
ngOnInit() {
|
|
61
|
-
this.inputId = `bbq-${this.inputWidget.action.replace(/\s+/g, '-').toLowerCase()}-input`;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { Component, Input, OnInit } from '@angular/core';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
|
-
import { FormsModule } from '@angular/forms';
|
|
4
|
-
import type { MultiSelectWidget } from '@bbq-chat/widgets';
|
|
5
|
-
import { CustomWidgetComponent } from '../custom-widget-renderer.types';
|
|
6
|
-
|
|
7
|
-
@Component({
|
|
8
|
-
selector: 'bbq-multiselect-widget',
|
|
9
|
-
standalone: true,
|
|
10
|
-
imports: [CommonModule, FormsModule],
|
|
11
|
-
template: `
|
|
12
|
-
<div
|
|
13
|
-
class="bbq-widget bbq-multi-select"
|
|
14
|
-
[attr.data-widget-type]="'multiselect'">
|
|
15
|
-
<label *ngIf="showLabel" class="bbq-multi-select-label" [attr.for]="selectId">
|
|
16
|
-
{{ multiSelectWidget.label }}
|
|
17
|
-
</label>
|
|
18
|
-
<select
|
|
19
|
-
[id]="selectId"
|
|
20
|
-
[ngClass]="selectClasses"
|
|
21
|
-
[attr.data-action]="multiSelectWidget.action"
|
|
22
|
-
multiple
|
|
23
|
-
[(ngModel)]="values">
|
|
24
|
-
@for (option of multiSelectWidget.options; track option) {
|
|
25
|
-
<option [value]="option">{{ option }}</option>
|
|
26
|
-
}
|
|
27
|
-
</select>
|
|
28
|
-
</div>
|
|
29
|
-
`,
|
|
30
|
-
styles: []
|
|
31
|
-
})
|
|
32
|
-
export class MultiSelectWidgetComponent implements CustomWidgetComponent, OnInit {
|
|
33
|
-
@Input() widget!: any;
|
|
34
|
-
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
35
|
-
|
|
36
|
-
values: string[] = [];
|
|
37
|
-
selectId = '';
|
|
38
|
-
|
|
39
|
-
get multiSelectWidget(): MultiSelectWidget {
|
|
40
|
-
return this.widget as MultiSelectWidget;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
get showLabel(): boolean {
|
|
44
|
-
const widget = this.multiSelectWidget as any;
|
|
45
|
-
if (widget.hideLabel === true) {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
if (widget.showLabel === false) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get selectClasses(): string[] {
|
|
55
|
-
return this.isFormAppearance
|
|
56
|
-
? ['bbq-form-multiselect', 'bbq-form-select']
|
|
57
|
-
: ['bbq-form-multiselect', 'bbq-form-select'];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private get isFormAppearance(): boolean {
|
|
61
|
-
return (this.multiSelectWidget as any).appearance === 'form';
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
ngOnInit() {
|
|
65
|
-
this.selectId = `bbq-${this.multiSelectWidget.action.replace(/\s+/g, '-').toLowerCase()}-select`;
|
|
66
|
-
}
|
|
67
|
-
}
|