@bbq-chat/widgets-angular 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.angular/cache/21.0.5/ng-packagr/97cbacd0e5e4cb18d1fead4d7f3aee1c3863ba3ffbe7cb7dd7780f237a848a5c +1 -0
  2. package/.angular/cache/21.0.5/ng-packagr/tsbuildinfo/index.tsbuildinfo +1 -0
  3. package/.eslintrc.json +23 -0
  4. package/.prettierrc.json +8 -0
  5. package/EXAMPLES.md +484 -0
  6. package/README.md +119 -0
  7. package/angular.json +36 -0
  8. package/ng-package.json +9 -0
  9. package/package.json +4 -25
  10. package/src/angular-widget-renderer.spec.ts +157 -0
  11. package/src/components/button.component.ts +35 -0
  12. package/src/components/card.component.ts +52 -0
  13. package/src/components/datepicker.component.ts +63 -0
  14. package/src/components/dropdown.component.ts +65 -0
  15. package/src/components/fileupload.component.ts +71 -0
  16. package/src/components/form.component.ts +433 -0
  17. package/src/components/image.component.ts +33 -0
  18. package/src/components/imagecollection.component.ts +39 -0
  19. package/src/components/index.ts +20 -0
  20. package/src/components/input.component.ts +63 -0
  21. package/src/components/multiselect.component.ts +67 -0
  22. package/src/components/progressbar.component.ts +50 -0
  23. package/src/components/slider.component.ts +67 -0
  24. package/src/components/textarea.component.ts +63 -0
  25. package/src/components/themeswitcher.component.ts +46 -0
  26. package/src/components/toggle.component.ts +63 -0
  27. package/src/custom-widget-renderer.types.ts +120 -0
  28. package/src/examples/form-validation-listener.component.ts +41 -0
  29. package/src/public_api.ts +107 -0
  30. package/src/renderers/AngularWidgetRenderer.ts +100 -0
  31. package/src/renderers/built-in-components.ts +41 -0
  32. package/src/renderers/index.ts +7 -0
  33. package/src/services/form-validation.service.ts +21 -0
  34. package/src/widget-di.tokens.ts +95 -0
  35. package/src/widget-registry.service.ts +128 -0
  36. package/src/widget-renderer.component.ts +421 -0
  37. package/tsconfig.json +37 -0
  38. package/tsconfig.lib.json +18 -0
  39. package/tsconfig.lib.prod.json +11 -0
  40. package/tsconfig.spec.json +13 -0
@@ -0,0 +1,157 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { AngularWidgetRenderer } from './renderers/AngularWidgetRenderer';
3
+ import { ButtonWidgetComponent } from './components/button.component';
4
+ import { InputWidgetComponent } from './components/input.component';
5
+ import { CardWidgetComponent } from './components/card.component';
6
+
7
+ describe('AngularWidgetRenderer', () => {
8
+ it('should create an instance', () => {
9
+ const renderer = new AngularWidgetRenderer();
10
+ expect(renderer).toBeTruthy();
11
+ expect(renderer.framework).toBe('Angular');
12
+ });
13
+
14
+ it('should register components', () => {
15
+ const renderer = new AngularWidgetRenderer();
16
+ renderer.registerComponent('button', ButtonWidgetComponent);
17
+
18
+ const mockWidget = {
19
+ type: 'button',
20
+ label: 'Test',
21
+ action: 'test_action',
22
+ toJson: () => '{}',
23
+ toObject: () => ({})
24
+ };
25
+
26
+ const componentType = renderer.getComponentType(mockWidget);
27
+ expect(componentType).toBe(ButtonWidgetComponent);
28
+ });
29
+
30
+ it('should register multiple components at once', () => {
31
+ const renderer = new AngularWidgetRenderer();
32
+ renderer.registerComponents({
33
+ button: ButtonWidgetComponent,
34
+ input: InputWidgetComponent
35
+ });
36
+
37
+ const mockButtonWidget = {
38
+ type: 'button',
39
+ label: 'Test',
40
+ action: 'test_action',
41
+ toJson: () => '{}',
42
+ toObject: () => ({})
43
+ };
44
+
45
+ const mockInputWidget = {
46
+ type: 'input',
47
+ label: 'Test',
48
+ action: 'test_action',
49
+ toJson: () => '{}',
50
+ toObject: () => ({})
51
+ };
52
+
53
+ expect(renderer.getComponentType(mockButtonWidget)).toBe(ButtonWidgetComponent);
54
+ expect(renderer.getComponentType(mockInputWidget)).toBe(InputWidgetComponent);
55
+ });
56
+
57
+ it('should return null for unregistered widget types', () => {
58
+ const renderer = new AngularWidgetRenderer();
59
+
60
+ const mockWidget = {
61
+ type: 'unknown',
62
+ label: 'Test',
63
+ action: 'test_action',
64
+ toJson: () => '{}',
65
+ toObject: () => ({})
66
+ };
67
+
68
+ const componentType = renderer.getComponentType(mockWidget);
69
+ expect(componentType).toBeNull();
70
+ });
71
+
72
+ it('should respect custom overrides from constructor', () => {
73
+ const customComponent = ButtonWidgetComponent; // Use as a stand-in for custom component
74
+ const renderer = new AngularWidgetRenderer({
75
+ components: {
76
+ button: customComponent
77
+ }
78
+ });
79
+
80
+ const mockWidget = {
81
+ type: 'button',
82
+ label: 'Test',
83
+ action: 'test_action',
84
+ toJson: () => '{}',
85
+ toObject: () => ({})
86
+ };
87
+
88
+ const componentType = renderer.getComponentType(mockWidget);
89
+ expect(componentType).toBe(customComponent);
90
+ });
91
+
92
+ it('should prioritize constructor overrides over registered components', () => {
93
+ const constructorComponent = CardWidgetComponent;
94
+ const registeredComponent = ButtonWidgetComponent;
95
+
96
+ const renderer = new AngularWidgetRenderer({
97
+ components: {
98
+ button: constructorComponent
99
+ }
100
+ });
101
+
102
+ // Register a different component
103
+ renderer.registerComponent('button', registeredComponent);
104
+
105
+ const mockWidget = {
106
+ type: 'button',
107
+ label: 'Test',
108
+ action: 'test_action',
109
+ toJson: () => '{}',
110
+ toObject: () => ({})
111
+ };
112
+
113
+ // Constructor override should take precedence
114
+ const componentType = renderer.getComponentType(mockWidget);
115
+ expect(componentType).toBe(constructorComponent);
116
+ });
117
+
118
+ it('should register built-in components', () => {
119
+ const renderer = new AngularWidgetRenderer();
120
+
121
+ const builtInComponents = {
122
+ button: ButtonWidgetComponent,
123
+ input: InputWidgetComponent,
124
+ card: CardWidgetComponent
125
+ };
126
+
127
+ renderer.registerBuiltInComponents(builtInComponents);
128
+
129
+ const mockButtonWidget = {
130
+ type: 'button',
131
+ label: 'Test',
132
+ action: 'test_action',
133
+ toJson: () => '{}',
134
+ toObject: () => ({})
135
+ };
136
+
137
+ const mockInputWidget = {
138
+ type: 'input',
139
+ label: 'Test',
140
+ action: 'test_action',
141
+ toJson: () => '{}',
142
+ toObject: () => ({})
143
+ };
144
+
145
+ const mockCardWidget = {
146
+ type: 'card',
147
+ label: 'Test',
148
+ action: 'test_action',
149
+ toJson: () => '{}',
150
+ toObject: () => ({})
151
+ };
152
+
153
+ expect(renderer.getComponentType(mockButtonWidget)).toBe(ButtonWidgetComponent);
154
+ expect(renderer.getComponentType(mockInputWidget)).toBe(InputWidgetComponent);
155
+ expect(renderer.getComponentType(mockCardWidget)).toBe(CardWidgetComponent);
156
+ });
157
+ });
@@ -0,0 +1,35 @@
1
+ import { Component, Input } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import type { ButtonWidget } from '@bbq-chat/widgets';
4
+ import { CustomWidgetComponent } from '../custom-widget-renderer.types';
5
+
6
+ @Component({
7
+ selector: 'bbq-button-widget',
8
+ standalone: true,
9
+ imports: [CommonModule],
10
+ template: `
11
+ <button
12
+ class="bbq-widget bbq-button"
13
+ [attr.data-widget-type]="'button'"
14
+ [attr.data-action]="buttonWidget.action"
15
+ type="button"
16
+ (click)="onClick()">
17
+ {{ buttonWidget.label }}
18
+ </button>
19
+ `,
20
+ styles: []
21
+ })
22
+ export class ButtonWidgetComponent implements CustomWidgetComponent {
23
+ @Input() widget!: any;
24
+ widgetAction?: (actionName: string, payload: unknown) => void;
25
+
26
+ get buttonWidget(): ButtonWidget {
27
+ return this.widget as ButtonWidget;
28
+ }
29
+
30
+ onClick() {
31
+ if (this.widgetAction) {
32
+ this.widgetAction(this.buttonWidget.action, {});
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,52 @@
1
+ import { Component, Input } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import type { CardWidget } from '@bbq-chat/widgets';
4
+ import { CustomWidgetComponent } from '../custom-widget-renderer.types';
5
+
6
+ @Component({
7
+ selector: 'bbq-card-widget',
8
+ standalone: true,
9
+ imports: [CommonModule],
10
+ template: `
11
+ <div
12
+ class="bbq-widget bbq-card"
13
+ [attr.data-widget-type]="'card'"
14
+ [attr.data-action]="cardWidget.action"
15
+ role="article">
16
+ <h3 class="bbq-card-title">{{ cardWidget.title }}</h3>
17
+ @if (cardWidget.description) {
18
+ <p class="bbq-card-description">{{ cardWidget.description }}</p>
19
+ }
20
+ @if (cardWidget.imageUrl) {
21
+ <img
22
+ class="bbq-card-image"
23
+ [src]="cardWidget.imageUrl"
24
+ [alt]="cardWidget.title"
25
+ loading="lazy"
26
+ style="display:block;max-width:100%;height:auto;object-fit:cover;max-height:200px;border-radius:6px;margin-bottom:12px;" />
27
+ }
28
+ <button
29
+ class="bbq-card-action bbq-button"
30
+ [attr.data-action]="cardWidget.action"
31
+ type="button"
32
+ (click)="onClick()">
33
+ {{ cardWidget.label }}
34
+ </button>
35
+ </div>
36
+ `,
37
+ styles: []
38
+ })
39
+ export class CardWidgetComponent implements CustomWidgetComponent {
40
+ @Input() widget!: any;
41
+ widgetAction?: (actionName: string, payload: unknown) => void;
42
+
43
+ get cardWidget(): CardWidget {
44
+ return this.widget as CardWidget;
45
+ }
46
+
47
+ onClick() {
48
+ if (this.widgetAction) {
49
+ this.widgetAction(this.cardWidget.action, {});
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,63 @@
1
+ import { Component, Input, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import type { DatePickerWidget } from '@bbq-chat/widgets';
5
+ import { CustomWidgetComponent } from '../custom-widget-renderer.types';
6
+
7
+ @Component({
8
+ selector: 'bbq-datepicker-widget',
9
+ standalone: true,
10
+ imports: [CommonModule, FormsModule],
11
+ template: `
12
+ <div
13
+ class="bbq-widget bbq-date-picker"
14
+ [attr.data-widget-type]="'datepicker'">
15
+ <label *ngIf="showLabel" class="bbq-date-picker-label" [attr.for]="inputId">
16
+ {{ datePickerWidget.label }}
17
+ </label>
18
+ <input
19
+ type="date"
20
+ [id]="inputId"
21
+ [ngClass]="inputClasses"
22
+ [attr.data-action]="datePickerWidget.action"
23
+ [min]="datePickerWidget.minDate || ''"
24
+ [max]="datePickerWidget.maxDate || ''"
25
+ [(ngModel)]="value" />
26
+ </div>
27
+ `,
28
+ styles: []
29
+ })
30
+ export class DatePickerWidgetComponent implements CustomWidgetComponent, OnInit {
31
+ @Input() widget!: any;
32
+ widgetAction?: (actionName: string, payload: unknown) => void;
33
+
34
+ value = '';
35
+ inputId = '';
36
+
37
+ get datePickerWidget(): DatePickerWidget {
38
+ return this.widget as DatePickerWidget;
39
+ }
40
+
41
+ get showLabel(): boolean {
42
+ const widget = this.datePickerWidget 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-datepicker'] : ['bbq-form-datepicker', 'bbq-input'];
54
+ }
55
+
56
+ private get isFormAppearance(): boolean {
57
+ return (this.datePickerWidget as any).appearance === 'form';
58
+ }
59
+
60
+ ngOnInit() {
61
+ this.inputId = `bbq-${this.datePickerWidget.action.replace(/\s+/g, '-').toLowerCase()}-date`;
62
+ }
63
+ }
@@ -0,0 +1,65 @@
1
+ import { Component, Input, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import type { DropdownWidget } from '@bbq-chat/widgets';
5
+ import { CustomWidgetComponent } from '../custom-widget-renderer.types';
6
+
7
+ @Component({
8
+ selector: 'bbq-dropdown-widget',
9
+ standalone: true,
10
+ imports: [CommonModule, FormsModule],
11
+ template: `
12
+ <div
13
+ class="bbq-widget bbq-dropdown"
14
+ [attr.data-widget-type]="'dropdown'">
15
+ <label *ngIf="showLabel" class="bbq-dropdown-label" [attr.for]="selectId">
16
+ {{ dropdownWidget.label }}
17
+ </label>
18
+ <select
19
+ [id]="selectId"
20
+ [ngClass]="selectClasses"
21
+ [attr.data-action]="dropdownWidget.action"
22
+ [(ngModel)]="value">
23
+ @for (option of dropdownWidget.options; track option) {
24
+ <option [value]="option">{{ option }}</option>
25
+ }
26
+ </select>
27
+ </div>
28
+ `,
29
+ styles: []
30
+ })
31
+ export class DropdownWidgetComponent implements CustomWidgetComponent, OnInit {
32
+ @Input() widget!: any;
33
+ widgetAction?: (actionName: string, payload: unknown) => void;
34
+
35
+ value = '';
36
+ selectId = '';
37
+
38
+ get dropdownWidget(): DropdownWidget {
39
+ return this.widget as DropdownWidget;
40
+ }
41
+
42
+ get showLabel(): boolean {
43
+ const widget = this.dropdownWidget as any;
44
+ if (widget.hideLabel === true) {
45
+ return false;
46
+ }
47
+ if (widget.showLabel === false) {
48
+ return false;
49
+ }
50
+ return true;
51
+ }
52
+
53
+ get selectClasses(): string[] {
54
+ return this.isFormAppearance ? ['bbq-form-select'] : ['bbq-dropdown'];
55
+ }
56
+
57
+ private get isFormAppearance(): boolean {
58
+ return (this.dropdownWidget as any).appearance === 'form';
59
+ }
60
+
61
+ ngOnInit() {
62
+ this.selectId = `bbq-${this.dropdownWidget.action.replace(/\s+/g, '-').toLowerCase()}-select`;
63
+ this.value = this.dropdownWidget.options[0] || '';
64
+ }
65
+ }
@@ -0,0 +1,71 @@
1
+ import { Component, Input, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import type { FileUploadWidget } from '@bbq-chat/widgets';
4
+ import { CustomWidgetComponent } from '../custom-widget-renderer.types';
5
+
6
+ @Component({
7
+ selector: 'bbq-fileupload-widget',
8
+ standalone: true,
9
+ imports: [CommonModule],
10
+ template: `
11
+ <div
12
+ class="bbq-widget bbq-file-upload"
13
+ [attr.data-widget-type]="'fileupload'">
14
+ <label *ngIf="showLabel" class="bbq-file-label" [attr.for]="inputId">
15
+ {{ fileUploadWidget.label }}
16
+ </label>
17
+ <input
18
+ type="file"
19
+ [id]="inputId"
20
+ [ngClass]="inputClasses"
21
+ [attr.data-action]="fileUploadWidget.action"
22
+ [accept]="fileUploadWidget.accept || ''"
23
+ [attr.data-max-bytes]="fileUploadWidget.maxBytes"
24
+ (change)="onFileChange($event)" />
25
+ </div>
26
+ `,
27
+ styles: []
28
+ })
29
+ export class FileUploadWidgetComponent implements CustomWidgetComponent, OnInit {
30
+ @Input() widget!: any;
31
+ widgetAction?: (actionName: string, payload: unknown) => void;
32
+
33
+ inputId = '';
34
+
35
+ get fileUploadWidget(): FileUploadWidget {
36
+ return this.widget as FileUploadWidget;
37
+ }
38
+
39
+ get showLabel(): boolean {
40
+ const widget = this.fileUploadWidget as any;
41
+ if (widget.hideLabel === true) {
42
+ return false;
43
+ }
44
+ if (widget.showLabel === false) {
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+
50
+ get inputClasses(): string[] {
51
+ return this.isFormAppearance ? ['bbq-form-fileupload'] : ['bbq-file'];
52
+ }
53
+
54
+ private get isFormAppearance(): boolean {
55
+ return (this.fileUploadWidget as any).appearance === 'form';
56
+ }
57
+
58
+ ngOnInit() {
59
+ this.inputId = `bbq-${this.fileUploadWidget.action.replace(/\s+/g, '-').toLowerCase()}-file`;
60
+ }
61
+
62
+ onFileChange(event: Event) {
63
+ const target = event.target as HTMLInputElement;
64
+ if (target.files && target.files.length > 0) {
65
+ const file = target.files[0];
66
+ if (this.widgetAction) {
67
+ this.widgetAction(this.fileUploadWidget.action, { file });
68
+ }
69
+ }
70
+ }
71
+ }