@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.
- package/.angular/cache/21.0.5/ng-packagr/97cbacd0e5e4cb18d1fead4d7f3aee1c3863ba3ffbe7cb7dd7780f237a848a5c +1 -0
- package/.angular/cache/21.0.5/ng-packagr/tsbuildinfo/index.tsbuildinfo +1 -0
- package/.eslintrc.json +23 -0
- package/.prettierrc.json +8 -0
- package/EXAMPLES.md +484 -0
- package/README.md +119 -0
- package/angular.json +36 -0
- package/ng-package.json +9 -0
- package/package.json +4 -25
- package/src/angular-widget-renderer.spec.ts +157 -0
- package/src/components/button.component.ts +35 -0
- package/src/components/card.component.ts +52 -0
- package/src/components/datepicker.component.ts +63 -0
- package/src/components/dropdown.component.ts +65 -0
- package/src/components/fileupload.component.ts +71 -0
- package/src/components/form.component.ts +433 -0
- package/src/components/image.component.ts +33 -0
- package/src/components/imagecollection.component.ts +39 -0
- package/src/components/index.ts +20 -0
- package/src/components/input.component.ts +63 -0
- package/src/components/multiselect.component.ts +67 -0
- package/src/components/progressbar.component.ts +50 -0
- package/src/components/slider.component.ts +67 -0
- package/src/components/textarea.component.ts +63 -0
- package/src/components/themeswitcher.component.ts +46 -0
- package/src/components/toggle.component.ts +63 -0
- package/src/custom-widget-renderer.types.ts +120 -0
- package/src/examples/form-validation-listener.component.ts +41 -0
- package/src/public_api.ts +107 -0
- package/src/renderers/AngularWidgetRenderer.ts +100 -0
- package/src/renderers/built-in-components.ts +41 -0
- package/src/renderers/index.ts +7 -0
- package/src/services/form-validation.service.ts +21 -0
- package/src/widget-di.tokens.ts +95 -0
- package/src/widget-registry.service.ts +128 -0
- package/src/widget-renderer.component.ts +421 -0
- package/tsconfig.json +37 -0
- package/tsconfig.lib.json +18 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +13 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Component, Input, OnInit } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import type { ProgressBarWidget } from '@bbq-chat/widgets';
|
|
4
|
+
import { CustomWidgetComponent } from '../custom-widget-renderer.types';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'bbq-progressbar-widget',
|
|
8
|
+
standalone: true,
|
|
9
|
+
imports: [CommonModule],
|
|
10
|
+
template: `
|
|
11
|
+
<div
|
|
12
|
+
class="bbq-widget bbq-progress-bar"
|
|
13
|
+
[attr.data-widget-type]="'progressbar'">
|
|
14
|
+
<label class="bbq-progress-bar-label" [attr.for]="progressId">
|
|
15
|
+
{{ progressBarWidget.label }}
|
|
16
|
+
</label>
|
|
17
|
+
<progress
|
|
18
|
+
[id]="progressId"
|
|
19
|
+
class="bbq-progress-bar-element"
|
|
20
|
+
[value]="progressBarWidget.value"
|
|
21
|
+
[max]="progressBarWidget.max"
|
|
22
|
+
[attr.data-action]="progressBarWidget.action"
|
|
23
|
+
[attr.aria-label]="progressBarWidget.label"
|
|
24
|
+
[attr.aria-valuenow]="progressBarWidget.value"
|
|
25
|
+
[attr.aria-valuemin]="0"
|
|
26
|
+
[attr.aria-valuemax]="progressBarWidget.max">
|
|
27
|
+
{{ percentage }}%
|
|
28
|
+
</progress>
|
|
29
|
+
<span class="bbq-progress-bar-value" aria-live="polite">{{ percentage }}%</span>
|
|
30
|
+
</div>
|
|
31
|
+
`,
|
|
32
|
+
styles: []
|
|
33
|
+
})
|
|
34
|
+
export class ProgressBarWidgetComponent implements CustomWidgetComponent, OnInit {
|
|
35
|
+
@Input() widget!: any;
|
|
36
|
+
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
37
|
+
|
|
38
|
+
progressId = '';
|
|
39
|
+
percentage = 0;
|
|
40
|
+
|
|
41
|
+
get progressBarWidget(): ProgressBarWidget {
|
|
42
|
+
return this.widget as ProgressBarWidget;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ngOnInit() {
|
|
46
|
+
this.progressId = `bbq-${this.progressBarWidget.action.replace(/\s+/g, '-').toLowerCase()}-progress`;
|
|
47
|
+
const max = this.progressBarWidget.max;
|
|
48
|
+
this.percentage = max > 0 ? Math.floor((this.progressBarWidget.value * 100) / max) : 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Component, Input, OnInit } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
import type { SliderWidget } from '@bbq-chat/widgets';
|
|
5
|
+
import { CustomWidgetComponent } from '../custom-widget-renderer.types';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'bbq-slider-widget',
|
|
9
|
+
standalone: true,
|
|
10
|
+
imports: [CommonModule, FormsModule],
|
|
11
|
+
template: `
|
|
12
|
+
<div
|
|
13
|
+
class="bbq-widget bbq-slider"
|
|
14
|
+
[attr.data-widget-type]="'slider'">
|
|
15
|
+
<label *ngIf="showLabel" class="bbq-slider-label" [attr.for]="sliderId">
|
|
16
|
+
{{ sliderWidget.label }}
|
|
17
|
+
</label>
|
|
18
|
+
<input
|
|
19
|
+
type="range"
|
|
20
|
+
[id]="sliderId"
|
|
21
|
+
[ngClass]="sliderClasses"
|
|
22
|
+
[min]="sliderWidget.min"
|
|
23
|
+
[max]="sliderWidget.max"
|
|
24
|
+
[step]="sliderWidget.step"
|
|
25
|
+
[attr.data-action]="sliderWidget.action"
|
|
26
|
+
[attr.aria-label]="sliderWidget.label"
|
|
27
|
+
[(ngModel)]="value" />
|
|
28
|
+
<span class="bbq-slider-value" aria-live="polite">{{ value }}</span>
|
|
29
|
+
</div>
|
|
30
|
+
`,
|
|
31
|
+
styles: []
|
|
32
|
+
})
|
|
33
|
+
export class SliderWidgetComponent implements CustomWidgetComponent, OnInit {
|
|
34
|
+
@Input() widget!: any;
|
|
35
|
+
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
36
|
+
|
|
37
|
+
value: number = 0;
|
|
38
|
+
sliderId = '';
|
|
39
|
+
|
|
40
|
+
get sliderWidget(): SliderWidget {
|
|
41
|
+
return this.widget as SliderWidget;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get showLabel(): boolean {
|
|
45
|
+
const widget = this.sliderWidget as any;
|
|
46
|
+
if (widget.hideLabel === true) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (widget.showLabel === false) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get sliderClasses(): string[] {
|
|
56
|
+
return this.isFormAppearance ? ['bbq-form-slider'] : ['bbq-slider'];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private get isFormAppearance(): boolean {
|
|
60
|
+
return (this.sliderWidget as any).appearance === 'form';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ngOnInit() {
|
|
64
|
+
this.sliderId = `bbq-${this.sliderWidget.action.replace(/\s+/g, '-').toLowerCase()}-slider`;
|
|
65
|
+
this.value = this.sliderWidget.defaultValue ?? this.sliderWidget.min;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -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 { TextAreaWidget } from '@bbq-chat/widgets';
|
|
5
|
+
import { CustomWidgetComponent } from '../custom-widget-renderer.types';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'bbq-textarea-widget',
|
|
9
|
+
standalone: true,
|
|
10
|
+
imports: [CommonModule, FormsModule],
|
|
11
|
+
template: `
|
|
12
|
+
<div
|
|
13
|
+
class="bbq-widget bbq-textarea"
|
|
14
|
+
[attr.data-widget-type]="'textarea'">
|
|
15
|
+
<label *ngIf="showLabel" class="bbq-textarea-label" [attr.for]="textareaId">
|
|
16
|
+
{{ textareaWidget.label }}
|
|
17
|
+
</label>
|
|
18
|
+
<textarea
|
|
19
|
+
[id]="textareaId"
|
|
20
|
+
[ngClass]="textareaClasses"
|
|
21
|
+
[attr.data-action]="textareaWidget.action"
|
|
22
|
+
[placeholder]="textareaWidget.placeholder || ''"
|
|
23
|
+
[maxLength]="textareaWidget.maxLength || 0"
|
|
24
|
+
[rows]="textareaWidget.rows || 4"
|
|
25
|
+
[(ngModel)]="value"></textarea>
|
|
26
|
+
</div>
|
|
27
|
+
`,
|
|
28
|
+
styles: []
|
|
29
|
+
})
|
|
30
|
+
export class TextAreaWidgetComponent implements CustomWidgetComponent, OnInit {
|
|
31
|
+
@Input() widget!: any;
|
|
32
|
+
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
33
|
+
|
|
34
|
+
value = '';
|
|
35
|
+
textareaId = '';
|
|
36
|
+
|
|
37
|
+
get textareaWidget(): TextAreaWidget {
|
|
38
|
+
return this.widget as TextAreaWidget;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get showLabel(): boolean {
|
|
42
|
+
const widget = this.textareaWidget 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 textareaClasses(): string[] {
|
|
53
|
+
return this.isFormAppearance ? ['bbq-form-textarea'] : ['bbq-form-textarea', 'bbq-input'];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private get isFormAppearance(): boolean {
|
|
57
|
+
return (this.textareaWidget as any).appearance === 'form';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
ngOnInit() {
|
|
61
|
+
this.textareaId = `bbq-${this.textareaWidget.action.replace(/\s+/g, '-').toLowerCase()}-textarea`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Component, Input, OnInit } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
import type { ThemeSwitcherWidget } from '@bbq-chat/widgets';
|
|
5
|
+
import { CustomWidgetComponent } from '../custom-widget-renderer.types';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'bbq-themeswitcher-widget',
|
|
9
|
+
standalone: true,
|
|
10
|
+
imports: [CommonModule, FormsModule],
|
|
11
|
+
template: `
|
|
12
|
+
<div
|
|
13
|
+
class="bbq-widget bbq-theme-switcher"
|
|
14
|
+
[attr.data-widget-type]="'themeswitcher'">
|
|
15
|
+
<label class="bbq-theme-switcher-label" [attr.for]="selectId">
|
|
16
|
+
{{ themeSwitcherWidget.label }}
|
|
17
|
+
</label>
|
|
18
|
+
<select
|
|
19
|
+
[id]="selectId"
|
|
20
|
+
class="bbq-theme-switcher-select"
|
|
21
|
+
[attr.data-action]="themeSwitcherWidget.action"
|
|
22
|
+
[(ngModel)]="value">
|
|
23
|
+
@for (theme of themeSwitcherWidget.themes; track theme) {
|
|
24
|
+
<option [value]="theme">{{ theme }}</option>
|
|
25
|
+
}
|
|
26
|
+
</select>
|
|
27
|
+
</div>
|
|
28
|
+
`,
|
|
29
|
+
styles: []
|
|
30
|
+
})
|
|
31
|
+
export class ThemeSwitcherWidgetComponent implements CustomWidgetComponent, OnInit {
|
|
32
|
+
@Input() widget!: any;
|
|
33
|
+
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
34
|
+
|
|
35
|
+
value = '';
|
|
36
|
+
selectId = '';
|
|
37
|
+
|
|
38
|
+
get themeSwitcherWidget(): ThemeSwitcherWidget {
|
|
39
|
+
return this.widget as ThemeSwitcherWidget;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
ngOnInit() {
|
|
43
|
+
this.selectId = `bbq-${this.themeSwitcherWidget.action.replace(/\s+/g, '-').toLowerCase()}-select`;
|
|
44
|
+
this.value = this.themeSwitcherWidget.themes[0] || '';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -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 { ToggleWidget } from '@bbq-chat/widgets';
|
|
5
|
+
import { CustomWidgetComponent } from '../custom-widget-renderer.types';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'bbq-toggle-widget',
|
|
9
|
+
standalone: true,
|
|
10
|
+
imports: [CommonModule, FormsModule],
|
|
11
|
+
template: `
|
|
12
|
+
<div
|
|
13
|
+
class="bbq-widget bbq-toggle"
|
|
14
|
+
[attr.data-widget-type]="'toggle'">
|
|
15
|
+
<label class="bbq-toggle-label" [attr.for]="checkboxId">
|
|
16
|
+
<input
|
|
17
|
+
type="checkbox"
|
|
18
|
+
[id]="checkboxId"
|
|
19
|
+
[ngClass]="checkboxClasses"
|
|
20
|
+
[attr.data-action]="toggleWidget.action"
|
|
21
|
+
[attr.aria-label]="toggleWidget.label"
|
|
22
|
+
[(ngModel)]="checked" />
|
|
23
|
+
<span *ngIf="showLabel" class="bbq-toggle-text">{{ toggleWidget.label }}</span>
|
|
24
|
+
</label>
|
|
25
|
+
</div>
|
|
26
|
+
`,
|
|
27
|
+
styles: []
|
|
28
|
+
})
|
|
29
|
+
export class ToggleWidgetComponent implements CustomWidgetComponent, OnInit {
|
|
30
|
+
@Input() widget!: any;
|
|
31
|
+
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
32
|
+
|
|
33
|
+
checked = false;
|
|
34
|
+
checkboxId = '';
|
|
35
|
+
|
|
36
|
+
get toggleWidget(): ToggleWidget {
|
|
37
|
+
return this.widget as ToggleWidget;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get showLabel(): boolean {
|
|
41
|
+
const widget = this.toggleWidget as any;
|
|
42
|
+
if (widget.hideLabel === true) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (widget.showLabel === false) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get checkboxClasses(): string[] {
|
|
52
|
+
return this.isFormAppearance ? ['bbq-toggle-input', 'bbq-form-toggle'] : ['bbq-toggle-input'];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private get isFormAppearance(): boolean {
|
|
56
|
+
return (this.toggleWidget as any).appearance === 'form';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
ngOnInit() {
|
|
60
|
+
this.checkboxId = `bbq-${this.toggleWidget.action.replace(/\s+/g, '-').toLowerCase()}-checkbox`;
|
|
61
|
+
this.checked = this.toggleWidget.defaultValue ?? false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Type, TemplateRef } from '@angular/core';
|
|
2
|
+
import { ChatWidget } from '@bbq-chat/widgets';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Context provided to template-based custom widget renderers
|
|
6
|
+
*/
|
|
7
|
+
export interface WidgetTemplateContext {
|
|
8
|
+
/**
|
|
9
|
+
* The widget instance being rendered
|
|
10
|
+
*/
|
|
11
|
+
$implicit: ChatWidget;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The widget instance (alternative access)
|
|
15
|
+
*/
|
|
16
|
+
widget: ChatWidget;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Emit a widget action
|
|
20
|
+
*/
|
|
21
|
+
emitAction: (actionName: string, payload: unknown) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Interface for component-based custom widget renderers
|
|
26
|
+
*/
|
|
27
|
+
export interface CustomWidgetComponent {
|
|
28
|
+
/**
|
|
29
|
+
* The widget instance to render
|
|
30
|
+
*/
|
|
31
|
+
widget: ChatWidget;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Event emitter for widget actions (optional, will be set by the renderer)
|
|
35
|
+
*/
|
|
36
|
+
widgetAction?: (actionName: string, payload: unknown) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Type for custom widget renderer functions that return HTML strings
|
|
41
|
+
*/
|
|
42
|
+
export type CustomWidgetHtmlRenderer = (widget: ChatWidget) => string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Type for custom widget renderer configurations
|
|
46
|
+
*/
|
|
47
|
+
export type CustomWidgetRenderer =
|
|
48
|
+
| CustomWidgetHtmlRenderer
|
|
49
|
+
| Type<CustomWidgetComponent>
|
|
50
|
+
| TemplateRef<WidgetTemplateContext>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Configuration for registering a custom widget renderer
|
|
54
|
+
*/
|
|
55
|
+
export interface CustomWidgetRendererConfig {
|
|
56
|
+
/**
|
|
57
|
+
* The widget type identifier
|
|
58
|
+
*/
|
|
59
|
+
type: string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The renderer: can be a function returning HTML, an Angular Component class, or a TemplateRef
|
|
63
|
+
*/
|
|
64
|
+
renderer: CustomWidgetRenderer;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Type guard to check if a renderer is a TemplateRef
|
|
69
|
+
*/
|
|
70
|
+
export function isTemplateRenderer(
|
|
71
|
+
renderer: CustomWidgetRenderer
|
|
72
|
+
): renderer is TemplateRef<WidgetTemplateContext> {
|
|
73
|
+
return (
|
|
74
|
+
renderer !== null &&
|
|
75
|
+
typeof renderer === 'object' &&
|
|
76
|
+
'createEmbeddedView' in renderer
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Type guard to check if a renderer is an Angular Component
|
|
82
|
+
*
|
|
83
|
+
* Note: This uses a heuristic check based on the following assumptions:
|
|
84
|
+
* 1. Components are constructor functions
|
|
85
|
+
* 2. Components have a prototype with a constructor property
|
|
86
|
+
* 3. Components typically use dependency injection (no required constructor params)
|
|
87
|
+
*
|
|
88
|
+
* Limitation: This may not detect components with required constructor parameters.
|
|
89
|
+
* For edge cases, explicitly check your component's constructor signature.
|
|
90
|
+
*
|
|
91
|
+
* Alternative: You can always register a wrapper component that has no required params.
|
|
92
|
+
*/
|
|
93
|
+
export function isComponentRenderer(
|
|
94
|
+
renderer: CustomWidgetRenderer
|
|
95
|
+
): renderer is Type<CustomWidgetComponent> {
|
|
96
|
+
// Check if it's a function (constructor) but not a regular function renderer
|
|
97
|
+
if (typeof renderer !== 'function') {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for Angular component characteristics
|
|
102
|
+
// Components typically have prototype with constructor property
|
|
103
|
+
return (
|
|
104
|
+
renderer.prototype !== undefined &&
|
|
105
|
+
renderer.prototype.constructor === renderer &&
|
|
106
|
+
renderer.length === 0 // Constructor with no required params (Angular DI)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Type guard to check if a renderer is an HTML function
|
|
112
|
+
*
|
|
113
|
+
* Note: This should be checked AFTER checking for component and template renderers
|
|
114
|
+
* since components are also functions but with additional properties.
|
|
115
|
+
*/
|
|
116
|
+
export function isHtmlRenderer(
|
|
117
|
+
renderer: CustomWidgetRenderer
|
|
118
|
+
): renderer is CustomWidgetHtmlRenderer {
|
|
119
|
+
return typeof renderer === 'function';
|
|
120
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
|
2
|
+
import { JsonPipe } from '@angular/common';
|
|
3
|
+
import { Subscription } from 'rxjs';
|
|
4
|
+
import { FormValidationService, FormValidationEvent } from '../services/form-validation.service';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'bbq-form-validation-listener',
|
|
8
|
+
standalone: true,
|
|
9
|
+
imports: [JsonPipe],
|
|
10
|
+
template: `
|
|
11
|
+
<div class="bbq-validation-listener">
|
|
12
|
+
@if (lastEvent) {
|
|
13
|
+
<div>
|
|
14
|
+
<strong>Last validation (formId: {{ lastEvent.formId }})</strong>
|
|
15
|
+
<pre>{{ lastEvent | json }}</pre>
|
|
16
|
+
</div>
|
|
17
|
+
} @else {
|
|
18
|
+
<small>No validation events yet.</small>
|
|
19
|
+
}
|
|
20
|
+
</div>
|
|
21
|
+
`,
|
|
22
|
+
})
|
|
23
|
+
export class FormValidationListenerComponent implements OnInit, OnDestroy {
|
|
24
|
+
@Input() formId?: string;
|
|
25
|
+
lastEvent?: FormValidationEvent | null = null;
|
|
26
|
+
private sub?: Subscription;
|
|
27
|
+
|
|
28
|
+
constructor(private svc: FormValidationService) {}
|
|
29
|
+
|
|
30
|
+
ngOnInit() {
|
|
31
|
+
this.sub = this.svc.validation$.subscribe(ev => {
|
|
32
|
+
if (!this.formId || ev.formId === this.formId) {
|
|
33
|
+
this.lastEvent = ev;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
ngOnDestroy() {
|
|
39
|
+
this.sub?.unsubscribe();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bbq-chat/widgets-angular
|
|
3
|
+
*
|
|
4
|
+
* Angular components and services for BbQ ChatWidgets
|
|
5
|
+
*
|
|
6
|
+
* This package provides Angular-native components and services that wrap
|
|
7
|
+
* the core @bbq-chat/widgets library, making it easy to integrate chat
|
|
8
|
+
* widgets into Angular applications.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Export components
|
|
14
|
+
export { WidgetRendererComponent } from './widget-renderer.component';
|
|
15
|
+
|
|
16
|
+
// Export services
|
|
17
|
+
export { WidgetRegistryService } from './widget-registry.service';
|
|
18
|
+
export { FormValidationService } from './services/form-validation.service';
|
|
19
|
+
|
|
20
|
+
// Export DI tokens and factories
|
|
21
|
+
export {
|
|
22
|
+
WIDGET_EVENT_MANAGER_FACTORY,
|
|
23
|
+
SSR_WIDGET_RENDERER,
|
|
24
|
+
ANGULAR_WIDGET_RENDERER,
|
|
25
|
+
widgetEventManagerFactoryProvider,
|
|
26
|
+
ssrWidgetRendererFactory,
|
|
27
|
+
angularWidgetRendererFactory,
|
|
28
|
+
} from './widget-di.tokens';
|
|
29
|
+
|
|
30
|
+
export type { WidgetEventManagerFactory } from './widget-di.tokens';
|
|
31
|
+
|
|
32
|
+
// Export custom widget renderer types
|
|
33
|
+
export type {
|
|
34
|
+
CustomWidgetComponent,
|
|
35
|
+
CustomWidgetRenderer,
|
|
36
|
+
CustomWidgetHtmlRenderer,
|
|
37
|
+
CustomWidgetRendererConfig,
|
|
38
|
+
WidgetTemplateContext,
|
|
39
|
+
} from './custom-widget-renderer.types';
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
isHtmlRenderer,
|
|
43
|
+
isComponentRenderer,
|
|
44
|
+
isTemplateRenderer,
|
|
45
|
+
} from './custom-widget-renderer.types';
|
|
46
|
+
|
|
47
|
+
// Export Angular renderer and built-in components
|
|
48
|
+
export {
|
|
49
|
+
AngularWidgetRenderer,
|
|
50
|
+
ButtonWidgetComponent,
|
|
51
|
+
CardWidgetComponent,
|
|
52
|
+
InputWidgetComponent,
|
|
53
|
+
TextAreaWidgetComponent,
|
|
54
|
+
DropdownWidgetComponent,
|
|
55
|
+
SliderWidgetComponent,
|
|
56
|
+
ToggleWidgetComponent,
|
|
57
|
+
FileUploadWidgetComponent,
|
|
58
|
+
ThemeSwitcherWidgetComponent,
|
|
59
|
+
DatePickerWidgetComponent,
|
|
60
|
+
MultiSelectWidgetComponent,
|
|
61
|
+
ProgressBarWidgetComponent,
|
|
62
|
+
FormWidgetComponent,
|
|
63
|
+
ImageWidgetComponent,
|
|
64
|
+
ImageCollectionWidgetComponent,
|
|
65
|
+
} from './renderers';
|
|
66
|
+
|
|
67
|
+
export type {
|
|
68
|
+
AngularRendererOptions,
|
|
69
|
+
} from './renderers';
|
|
70
|
+
|
|
71
|
+
export { BUILT_IN_WIDGET_COMPONENTS } from './renderers/built-in-components';
|
|
72
|
+
|
|
73
|
+
// Re-export commonly used types and classes from core package
|
|
74
|
+
export {
|
|
75
|
+
ChatWidget,
|
|
76
|
+
} from '@bbq-chat/widgets';
|
|
77
|
+
|
|
78
|
+
export type {
|
|
79
|
+
ButtonWidget,
|
|
80
|
+
CardWidget,
|
|
81
|
+
FormWidget,
|
|
82
|
+
InputWidget,
|
|
83
|
+
TextAreaWidget,
|
|
84
|
+
DropdownWidget,
|
|
85
|
+
SliderWidget,
|
|
86
|
+
ToggleWidget,
|
|
87
|
+
FileUploadWidget,
|
|
88
|
+
DatePickerWidget,
|
|
89
|
+
MultiSelectWidget,
|
|
90
|
+
ProgressBarWidget,
|
|
91
|
+
ThemeSwitcherWidget,
|
|
92
|
+
ImageWidget,
|
|
93
|
+
ImageCollectionWidget,
|
|
94
|
+
} from '@bbq-chat/widgets';
|
|
95
|
+
|
|
96
|
+
// Re-export utilities
|
|
97
|
+
export {
|
|
98
|
+
SsrWidgetRenderer,
|
|
99
|
+
WidgetEventManager,
|
|
100
|
+
customWidgetRegistry,
|
|
101
|
+
} from '@bbq-chat/widgets';
|
|
102
|
+
|
|
103
|
+
// Examples
|
|
104
|
+
export { FormValidationListenerComponent } from './examples/form-validation-listener.component';
|
|
105
|
+
|
|
106
|
+
// Version
|
|
107
|
+
export const VERSION = '1.0.9';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Type } from '@angular/core';
|
|
2
|
+
import { IWidgetRenderer } from '@bbq-chat/widgets';
|
|
3
|
+
import type { ChatWidget } from '@bbq-chat/widgets';
|
|
4
|
+
import { CustomWidgetComponent } from '../custom-widget-renderer.types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for configuring the Angular widget renderer
|
|
8
|
+
*/
|
|
9
|
+
export interface AngularRendererOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Per-widget-type component overrides. Key is widget.type.
|
|
12
|
+
*/
|
|
13
|
+
components?: Partial<Record<string, Type<CustomWidgetComponent>>>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Angular widget renderer
|
|
18
|
+
* Returns Angular component types for dynamic rendering
|
|
19
|
+
* Provides feature parity with SsrWidgetRenderer but uses Angular components
|
|
20
|
+
*/
|
|
21
|
+
export class AngularWidgetRenderer implements IWidgetRenderer {
|
|
22
|
+
readonly framework = 'Angular';
|
|
23
|
+
private overrides: AngularRendererOptions['components'] | undefined;
|
|
24
|
+
private componentRegistry: Map<string, Type<CustomWidgetComponent>> = new Map();
|
|
25
|
+
|
|
26
|
+
constructor(options?: AngularRendererOptions) {
|
|
27
|
+
this.overrides = options?.components;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Register all built-in widget components
|
|
32
|
+
* Must be called after components are imported to avoid circular dependencies
|
|
33
|
+
*/
|
|
34
|
+
registerBuiltInComponents(components: Record<string, Type<CustomWidgetComponent>>) {
|
|
35
|
+
for (const [type, component] of Object.entries(components)) {
|
|
36
|
+
this.componentRegistry.set(type, component);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register or override a widget component
|
|
42
|
+
* Use this to replace built-in components or add custom ones
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* renderer.registerComponent('button', MyCustomButtonComponent);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
registerComponent(type: string, component: Type<CustomWidgetComponent>) {
|
|
50
|
+
this.componentRegistry.set(type, component);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Register multiple widget components at once
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* renderer.registerComponents({
|
|
59
|
+
* button: MyButtonComponent,
|
|
60
|
+
* card: MyCardComponent
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
registerComponents(components: Record<string, Type<CustomWidgetComponent>>) {
|
|
65
|
+
for (const [type, component] of Object.entries(components)) {
|
|
66
|
+
this.componentRegistry.set(type, component);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get the Angular component type for a given widget
|
|
72
|
+
* Returns the component class that should be dynamically instantiated
|
|
73
|
+
*/
|
|
74
|
+
getComponentType(widget: ChatWidget): Type<CustomWidgetComponent> | null {
|
|
75
|
+
const type = widget.type;
|
|
76
|
+
|
|
77
|
+
// Check for custom override first
|
|
78
|
+
if (this.overrides && this.overrides[type]) {
|
|
79
|
+
return this.overrides[type] as Type<CustomWidgetComponent>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check built-in registry
|
|
83
|
+
if (this.componentRegistry.has(type)) {
|
|
84
|
+
return this.componentRegistry.get(type)!;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Legacy method for IWidgetRenderer interface compatibility
|
|
92
|
+
* Not used in Angular rendering but required by interface
|
|
93
|
+
* @deprecated Use getComponentType() instead for Angular rendering
|
|
94
|
+
*/
|
|
95
|
+
renderWidget(widget: ChatWidget): string {
|
|
96
|
+
// This method is not used in Angular rendering
|
|
97
|
+
// It's only here for interface compatibility
|
|
98
|
+
return `<!-- Angular component rendering for ${widget.type} -->`;
|
|
99
|
+
}
|
|
100
|
+
}
|