@memberjunction/ng-forms 0.0.1 → 5.12.0
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/dist/lib/dynamic-form/dynamic-form.component.d.ts +116 -0
- package/dist/lib/dynamic-form/dynamic-form.component.d.ts.map +1 -0
- package/dist/lib/dynamic-form/dynamic-form.component.js +258 -0
- package/dist/lib/dynamic-form/dynamic-form.component.js.map +1 -0
- package/dist/lib/dynamic-form-field/dynamic-form-field.component.d.ts +111 -0
- package/dist/lib/dynamic-form-field/dynamic-form-field.component.d.ts.map +1 -0
- package/dist/lib/dynamic-form-field/dynamic-form-field.component.js +240 -0
- package/dist/lib/dynamic-form-field/dynamic-form-field.component.js.map +1 -0
- package/dist/lib/dynamic-form-response/dynamic-form-response.component.d.ts +43 -0
- package/dist/lib/dynamic-form-response/dynamic-form-response.component.d.ts.map +1 -0
- package/dist/lib/dynamic-form-response/dynamic-form-response.component.js +129 -0
- package/dist/lib/dynamic-form-response/dynamic-form-response.component.js.map +1 -0
- package/dist/lib/dynamic-form-response/form-response-utils.d.ts +74 -0
- package/dist/lib/dynamic-form-response/form-response-utils.d.ts.map +1 -0
- package/dist/lib/dynamic-form-response/form-response-utils.js +147 -0
- package/dist/lib/dynamic-form-response/form-response-utils.js.map +1 -0
- package/dist/lib/forms.module.d.ts +15 -0
- package/dist/lib/forms.module.d.ts.map +1 -0
- package/dist/lib/forms.module.js +97 -0
- package/dist/lib/forms.module.js.map +1 -0
- package/dist/public-api.d.ts +52 -0
- package/dist/public-api.d.ts.map +1 -0
- package/dist/public-api.js +54 -0
- package/dist/public-api.js.map +1 -0
- package/package.json +43 -6
- package/README.md +0 -45
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Dynamic Form Component
|
|
3
|
+
*
|
|
4
|
+
* Top-level container that renders a complete form from an `AgentResponseForm`
|
|
5
|
+
* schema definition. Automatically builds a reactive `FormGroup`, handles
|
|
6
|
+
* validation, and emits structured response data on submission.
|
|
7
|
+
*
|
|
8
|
+
* Supports two rendering modes:
|
|
9
|
+
* - **Simple choice mode**: Single buttongroup/radio question with no title —
|
|
10
|
+
* renders as standalone buttons for quick one-click responses.
|
|
11
|
+
* - **Full form mode**: Title bar, description, multiple fields, and an action
|
|
12
|
+
* footer with either a Submit button or choice buttons.
|
|
13
|
+
*
|
|
14
|
+
* @example Basic usage
|
|
15
|
+
* ```html
|
|
16
|
+
* <mj-dynamic-form
|
|
17
|
+
* [FormDefinition]="responseForm"
|
|
18
|
+
* [Disabled]="isSaving"
|
|
19
|
+
* (FormSubmitted)="onFormSubmitted($event)">
|
|
20
|
+
* </mj-dynamic-form>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example With visibility control
|
|
24
|
+
* ```html
|
|
25
|
+
* <mj-dynamic-form
|
|
26
|
+
* [FormDefinition]="responseForm"
|
|
27
|
+
* [Visible]="isLastMessage && isOwner"
|
|
28
|
+
* (FormSubmitted)="onFormSubmitted($event)">
|
|
29
|
+
* </mj-dynamic-form>
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @module @memberjunction/ng-forms
|
|
33
|
+
*/
|
|
34
|
+
import { EventEmitter, OnInit } from '@angular/core';
|
|
35
|
+
import { FormGroup, FormControl } from '@angular/forms';
|
|
36
|
+
import { AgentResponseForm, FormQuestion, FormOption } from '@memberjunction/ai-core-plus';
|
|
37
|
+
import * as i0 from "@angular/core";
|
|
38
|
+
export declare class DynamicFormComponent implements OnInit {
|
|
39
|
+
/**
|
|
40
|
+
* The form schema that drives rendering.
|
|
41
|
+
* Contains title, description, questions array, and optional submit label.
|
|
42
|
+
*/
|
|
43
|
+
FormDefinition: AgentResponseForm;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the form is disabled (e.g. while saving).
|
|
46
|
+
* Disables all inputs and buttons.
|
|
47
|
+
*/
|
|
48
|
+
Disabled: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Controls overall visibility. When `false`, the component renders nothing.
|
|
51
|
+
* Defaults to `true` so the form is always shown unless explicitly hidden.
|
|
52
|
+
*/
|
|
53
|
+
Visible: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Emitted when the user submits the form. The payload is a key-value map
|
|
56
|
+
* where keys are question IDs and values are the user's responses.
|
|
57
|
+
*/
|
|
58
|
+
FormSubmitted: EventEmitter<Record<string, string | number | boolean | Date>>;
|
|
59
|
+
/** The reactive form group built from the form definition */
|
|
60
|
+
FormGroup: FormGroup;
|
|
61
|
+
/** Whether a submission is in progress */
|
|
62
|
+
IsSubmitting: boolean;
|
|
63
|
+
private cdr;
|
|
64
|
+
ngOnInit(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Whether this form renders as a simple choice (single buttongroup/radio, no title).
|
|
67
|
+
*/
|
|
68
|
+
get IsSimpleChoice(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The choice question that should render in the footer (if present).
|
|
71
|
+
* When a form has a buttongroup/radio question, those buttons replace
|
|
72
|
+
* the standard Submit button in the footer.
|
|
73
|
+
*/
|
|
74
|
+
get FooterChoiceQuestion(): FormQuestion | null;
|
|
75
|
+
/**
|
|
76
|
+
* Questions rendered in the main form area (excludes footer choice question).
|
|
77
|
+
*/
|
|
78
|
+
get MainQuestions(): FormQuestion[];
|
|
79
|
+
/**
|
|
80
|
+
* Whether to show the standard Submit button (true when no footer choice question).
|
|
81
|
+
*/
|
|
82
|
+
get ShowSubmitButton(): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Whether the form should be visible (has questions and Visible is true).
|
|
85
|
+
*/
|
|
86
|
+
get IsVisible(): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Text for the submit button. Falls back to "Submit" if not specified.
|
|
89
|
+
*/
|
|
90
|
+
get SubmitLabel(): string;
|
|
91
|
+
/** Get the FormControl for a specific question by ID */
|
|
92
|
+
GetControl(questionId: string): FormControl;
|
|
93
|
+
/** Handle simple choice button click (single-question forms) */
|
|
94
|
+
OnSimpleChoiceClick(value: string | number | boolean): void;
|
|
95
|
+
/** Whether a given option value is the currently selected simple choice */
|
|
96
|
+
IsSimpleChoiceSelected(value: string | number | boolean): boolean;
|
|
97
|
+
/** Handle footer choice button click — selects the option and emits the form data */
|
|
98
|
+
OnFooterChoiceClick(value: string | number | boolean): void;
|
|
99
|
+
/** Whether a given option value is the currently selected footer choice */
|
|
100
|
+
IsFooterChoiceSelected(value: string | number | boolean): boolean;
|
|
101
|
+
/** Handle full form submission */
|
|
102
|
+
OnSubmit(): void;
|
|
103
|
+
/** Get options for a choice question */
|
|
104
|
+
GetOptions(question: FormQuestion): FormOption[];
|
|
105
|
+
/** Check if an option has an icon */
|
|
106
|
+
HasIcon(option: FormOption): boolean;
|
|
107
|
+
/** Track-by function for questions */
|
|
108
|
+
TrackByQuestionId(_index: number, question: FormQuestion): string;
|
|
109
|
+
/** Track-by function for options */
|
|
110
|
+
TrackByValue(_index: number, option: FormOption): string | number | boolean;
|
|
111
|
+
private isChoiceQuestion;
|
|
112
|
+
private buildFormGroup;
|
|
113
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DynamicFormComponent, never>;
|
|
114
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<DynamicFormComponent, "mj-dynamic-form", never, { "FormDefinition": { "alias": "FormDefinition"; "required": false; }; "Disabled": { "alias": "Disabled"; "required": false; }; "Visible": { "alias": "Visible"; "required": false; }; }, { "FormSubmitted": "FormSubmitted"; }, never, never, false, never>;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=dynamic-form.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-form.component.d.ts","sourceRoot":"","sources":["../../../src/lib/dynamic-form/dynamic-form.component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAA4B,YAAY,EAAE,MAAM,EAA6B,MAAM,eAAe,CAAC;AAC1G,OAAO,EAAE,SAAS,EAAE,WAAW,EAAc,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;;AAE3F,qBAMa,oBAAqB,YAAW,MAAM;IAC/C;;;OAGG;IACM,cAAc,EAAG,iBAAiB,CAAC;IAE5C;;;OAGG;IACM,QAAQ,UAAS;IAE1B;;;OAGG;IACM,OAAO,UAAQ;IAExB;;;OAGG;IACO,aAAa,iEAAwE;IAE/F,6DAA6D;IACtD,SAAS,EAAG,SAAS,CAAC;IAE7B,0CAA0C;IACnC,YAAY,UAAS;IAE5B,OAAO,CAAC,GAAG,CAA6B;IAExC,QAAQ,IAAI,IAAI;IAIhB;;OAEG;IACH,IAAW,cAAc,IAAI,OAAO,CAOnC;IAED;;;;OAIG;IACH,IAAW,oBAAoB,IAAI,YAAY,GAAG,IAAI,CAIrD;IAED;;OAEG;IACH,IAAW,aAAa,IAAI,YAAY,EAAE,CAKzC;IAED;;OAEG;IACH,IAAW,gBAAgB,IAAI,OAAO,CAErC;IAED;;OAEG;IACH,IAAW,SAAS,IAAI,OAAO,CAM9B;IAED;;OAEG;IACH,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED,wDAAwD;IACjD,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW;IAIlD,gEAAgE;IACzD,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI;IASlE,2EAA2E;IACpE,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO;IAOxE,qFAAqF;IAC9E,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI;IAsBlE,2EAA2E;IACpE,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO;IAMxE,kCAAkC;IAC3B,QAAQ,IAAI,IAAI;IAqBvB,wCAAwC;IACjC,UAAU,CAAC,QAAQ,EAAE,YAAY,GAAG,UAAU,EAAE;IAOvD,qCAAqC;IAC9B,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO;IAI3C,sCAAsC;IAC/B,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,MAAM;IAIxE,oCAAoC;IAC7B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO;IAIlF,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,cAAc;yCAtMb,oBAAoB;2CAApB,oBAAoB;CA2OhC"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Dynamic Form Component
|
|
3
|
+
*
|
|
4
|
+
* Top-level container that renders a complete form from an `AgentResponseForm`
|
|
5
|
+
* schema definition. Automatically builds a reactive `FormGroup`, handles
|
|
6
|
+
* validation, and emits structured response data on submission.
|
|
7
|
+
*
|
|
8
|
+
* Supports two rendering modes:
|
|
9
|
+
* - **Simple choice mode**: Single buttongroup/radio question with no title —
|
|
10
|
+
* renders as standalone buttons for quick one-click responses.
|
|
11
|
+
* - **Full form mode**: Title bar, description, multiple fields, and an action
|
|
12
|
+
* footer with either a Submit button or choice buttons.
|
|
13
|
+
*
|
|
14
|
+
* @example Basic usage
|
|
15
|
+
* ```html
|
|
16
|
+
* <mj-dynamic-form
|
|
17
|
+
* [FormDefinition]="responseForm"
|
|
18
|
+
* [Disabled]="isSaving"
|
|
19
|
+
* (FormSubmitted)="onFormSubmitted($event)">
|
|
20
|
+
* </mj-dynamic-form>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example With visibility control
|
|
24
|
+
* ```html
|
|
25
|
+
* <mj-dynamic-form
|
|
26
|
+
* [FormDefinition]="responseForm"
|
|
27
|
+
* [Visible]="isLastMessage && isOwner"
|
|
28
|
+
* (FormSubmitted)="onFormSubmitted($event)">
|
|
29
|
+
* </mj-dynamic-form>
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @module @memberjunction/ng-forms
|
|
33
|
+
*/
|
|
34
|
+
import { Component, Input, Output, EventEmitter, ChangeDetectorRef, inject } from '@angular/core';
|
|
35
|
+
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
|
36
|
+
import * as i0 from "@angular/core";
|
|
37
|
+
import * as i1 from "@angular/forms";
|
|
38
|
+
import * as i2 from "@progress/kendo-angular-buttons";
|
|
39
|
+
import * as i3 from "../dynamic-form-field/dynamic-form-field.component";
|
|
40
|
+
export class DynamicFormComponent {
|
|
41
|
+
/**
|
|
42
|
+
* The form schema that drives rendering.
|
|
43
|
+
* Contains title, description, questions array, and optional submit label.
|
|
44
|
+
*/
|
|
45
|
+
FormDefinition;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the form is disabled (e.g. while saving).
|
|
48
|
+
* Disables all inputs and buttons.
|
|
49
|
+
*/
|
|
50
|
+
Disabled = false;
|
|
51
|
+
/**
|
|
52
|
+
* Controls overall visibility. When `false`, the component renders nothing.
|
|
53
|
+
* Defaults to `true` so the form is always shown unless explicitly hidden.
|
|
54
|
+
*/
|
|
55
|
+
Visible = true;
|
|
56
|
+
/**
|
|
57
|
+
* Emitted when the user submits the form. The payload is a key-value map
|
|
58
|
+
* where keys are question IDs and values are the user's responses.
|
|
59
|
+
*/
|
|
60
|
+
FormSubmitted = new EventEmitter();
|
|
61
|
+
/** The reactive form group built from the form definition */
|
|
62
|
+
FormGroup;
|
|
63
|
+
/** Whether a submission is in progress */
|
|
64
|
+
IsSubmitting = false;
|
|
65
|
+
cdr = inject(ChangeDetectorRef);
|
|
66
|
+
ngOnInit() {
|
|
67
|
+
this.buildFormGroup();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Whether this form renders as a simple choice (single buttongroup/radio, no title).
|
|
71
|
+
*/
|
|
72
|
+
get IsSimpleChoice() {
|
|
73
|
+
if (!this.FormDefinition)
|
|
74
|
+
return false;
|
|
75
|
+
return (this.FormDefinition.questions.length === 1 &&
|
|
76
|
+
!this.FormDefinition.title &&
|
|
77
|
+
this.isChoiceQuestion(this.FormDefinition.questions[0]));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The choice question that should render in the footer (if present).
|
|
81
|
+
* When a form has a buttongroup/radio question, those buttons replace
|
|
82
|
+
* the standard Submit button in the footer.
|
|
83
|
+
*/
|
|
84
|
+
get FooterChoiceQuestion() {
|
|
85
|
+
if (!this.FormDefinition || this.IsSimpleChoice)
|
|
86
|
+
return null;
|
|
87
|
+
const choiceQuestions = this.FormDefinition.questions.filter(q => this.isChoiceQuestion(q));
|
|
88
|
+
return choiceQuestions.length > 0 ? choiceQuestions[choiceQuestions.length - 1] : null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Questions rendered in the main form area (excludes footer choice question).
|
|
92
|
+
*/
|
|
93
|
+
get MainQuestions() {
|
|
94
|
+
if (!this.FormDefinition)
|
|
95
|
+
return [];
|
|
96
|
+
const footerChoice = this.FooterChoiceQuestion;
|
|
97
|
+
if (!footerChoice)
|
|
98
|
+
return this.FormDefinition.questions;
|
|
99
|
+
return this.FormDefinition.questions.filter(q => q.id !== footerChoice.id);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Whether to show the standard Submit button (true when no footer choice question).
|
|
103
|
+
*/
|
|
104
|
+
get ShowSubmitButton() {
|
|
105
|
+
return !this.FooterChoiceQuestion;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Whether the form should be visible (has questions and Visible is true).
|
|
109
|
+
*/
|
|
110
|
+
get IsVisible() {
|
|
111
|
+
return (this.Visible &&
|
|
112
|
+
!!this.FormDefinition &&
|
|
113
|
+
this.FormDefinition.questions.length > 0);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Text for the submit button. Falls back to "Submit" if not specified.
|
|
117
|
+
*/
|
|
118
|
+
get SubmitLabel() {
|
|
119
|
+
return this.FormDefinition?.submitLabel || 'Submit';
|
|
120
|
+
}
|
|
121
|
+
/** Get the FormControl for a specific question by ID */
|
|
122
|
+
GetControl(questionId) {
|
|
123
|
+
return this.FormGroup.get(questionId);
|
|
124
|
+
}
|
|
125
|
+
/** Handle simple choice button click (single-question forms) */
|
|
126
|
+
OnSimpleChoiceClick(value) {
|
|
127
|
+
if (!this.Disabled) {
|
|
128
|
+
const question = this.FormDefinition.questions[0];
|
|
129
|
+
// Update the FormControl so external readers (e.g. collectFormData) can access the value
|
|
130
|
+
this.FormGroup.get(question.id)?.setValue(value);
|
|
131
|
+
this.FormSubmitted.emit({ [question.id]: value });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Whether a given option value is the currently selected simple choice */
|
|
135
|
+
IsSimpleChoiceSelected(value) {
|
|
136
|
+
if (!this.IsSimpleChoice)
|
|
137
|
+
return false;
|
|
138
|
+
const question = this.FormDefinition.questions[0];
|
|
139
|
+
const control = this.FormGroup.get(question.id);
|
|
140
|
+
return control?.value === value;
|
|
141
|
+
}
|
|
142
|
+
/** Handle footer choice button click — selects the option and emits the form data */
|
|
143
|
+
OnFooterChoiceClick(value) {
|
|
144
|
+
if (!this.Disabled && this.FooterChoiceQuestion) {
|
|
145
|
+
const mainControlKeys = this.MainQuestions.map(q => q.id);
|
|
146
|
+
let hasInvalidFields = false;
|
|
147
|
+
for (const key of mainControlKeys) {
|
|
148
|
+
const control = this.FormGroup.get(key);
|
|
149
|
+
if (control?.invalid) {
|
|
150
|
+
control.markAsTouched();
|
|
151
|
+
hasInvalidFields = true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (hasInvalidFields)
|
|
155
|
+
return;
|
|
156
|
+
// Update the FormControl so external readers (e.g. collectFormData) can access the value
|
|
157
|
+
this.FormGroup.get(this.FooterChoiceQuestion.id)?.setValue(value);
|
|
158
|
+
const formData = { ...this.FormGroup.value };
|
|
159
|
+
this.FormSubmitted.emit(formData);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** Whether a given option value is the currently selected footer choice */
|
|
163
|
+
IsFooterChoiceSelected(value) {
|
|
164
|
+
if (!this.FooterChoiceQuestion)
|
|
165
|
+
return false;
|
|
166
|
+
const control = this.FormGroup.get(this.FooterChoiceQuestion.id);
|
|
167
|
+
return control?.value === value;
|
|
168
|
+
}
|
|
169
|
+
/** Handle full form submission */
|
|
170
|
+
OnSubmit() {
|
|
171
|
+
if (this.FormGroup.invalid) {
|
|
172
|
+
Object.keys(this.FormGroup.controls).forEach(key => {
|
|
173
|
+
this.FormGroup.controls[key].markAsTouched();
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (!this.Disabled && !this.IsSubmitting) {
|
|
178
|
+
this.IsSubmitting = true;
|
|
179
|
+
this.FormSubmitted.emit(this.FormGroup.value);
|
|
180
|
+
// Reset submitting state after a short delay — the parent container
|
|
181
|
+
// is responsible for hiding the form or handling the response
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
this.IsSubmitting = false;
|
|
184
|
+
this.cdr.detectChanges();
|
|
185
|
+
}, 1000);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** Get options for a choice question */
|
|
189
|
+
GetOptions(question) {
|
|
190
|
+
if (typeof question.type === 'object' && 'options' in question.type) {
|
|
191
|
+
return question.type.options;
|
|
192
|
+
}
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
/** Check if an option has an icon */
|
|
196
|
+
HasIcon(option) {
|
|
197
|
+
return !!option?.icon;
|
|
198
|
+
}
|
|
199
|
+
/** Track-by function for questions */
|
|
200
|
+
TrackByQuestionId(_index, question) {
|
|
201
|
+
return question.id;
|
|
202
|
+
}
|
|
203
|
+
/** Track-by function for options */
|
|
204
|
+
TrackByValue(_index, option) {
|
|
205
|
+
return option.value;
|
|
206
|
+
}
|
|
207
|
+
isChoiceQuestion(question) {
|
|
208
|
+
const type = typeof question.type === 'string' ? question.type : question.type.type;
|
|
209
|
+
return ['buttongroup', 'radio'].includes(type);
|
|
210
|
+
}
|
|
211
|
+
buildFormGroup() {
|
|
212
|
+
const controls = {};
|
|
213
|
+
for (const question of this.FormDefinition.questions) {
|
|
214
|
+
const validators = [];
|
|
215
|
+
if (question.required) {
|
|
216
|
+
validators.push(Validators.required);
|
|
217
|
+
}
|
|
218
|
+
const questionType = typeof question.type === 'string' ? question.type : question.type.type;
|
|
219
|
+
if (questionType === 'email') {
|
|
220
|
+
validators.push(Validators.email);
|
|
221
|
+
}
|
|
222
|
+
if (['number', 'currency'].includes(questionType) && typeof question.type === 'object') {
|
|
223
|
+
if ('min' in question.type && question.type.min != null) {
|
|
224
|
+
validators.push(Validators.min(question.type.min));
|
|
225
|
+
}
|
|
226
|
+
if ('max' in question.type && question.type.max != null) {
|
|
227
|
+
validators.push(Validators.max(question.type.max));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
let initialValue = question.defaultValue;
|
|
231
|
+
if (!initialValue) {
|
|
232
|
+
if (questionType === 'checkbox' && typeof question.type === 'object' && 'multiple' in question.type && question.type.multiple) {
|
|
233
|
+
initialValue = [];
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
initialValue = null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
controls[question.id] = new FormControl(initialValue, validators);
|
|
240
|
+
}
|
|
241
|
+
this.FormGroup = new FormGroup(controls);
|
|
242
|
+
}
|
|
243
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
244
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: DynamicFormComponent, isStandalone: false, selector: "mj-dynamic-form", inputs: { FormDefinition: "FormDefinition", Disabled: "Disabled", Visible: "Visible" }, outputs: { FormSubmitted: "FormSubmitted" }, ngImport: i0, template: "@if (IsVisible) {\n <div class=\"dynamic-form\">\n <!-- Simple Choice Mode (single question, no title, buttongroup/radio) -->\n @if (IsSimpleChoice) {\n <div class=\"simple-choice-buttons\">\n @for (option of GetOptions(FormDefinition.questions[0]); track TrackByValue($index, option)) {\n <button\n kendoButton\n [themeColor]=\"IsSimpleChoiceSelected(option.value) ? 'primary' : 'base'\"\n [disabled]=\"Disabled\"\n (click)=\"OnSimpleChoiceClick(option.value)\"\n class=\"choice-button\"\n [class.selected]=\"IsSimpleChoiceSelected(option.value)\"\n >\n <span>\n @if (IsSimpleChoiceSelected(option.value)) {\n <i class=\"fa-solid fa-check\" aria-hidden=\"true\"></i>\n } @else if (HasIcon(option)) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </span>\n </button>\n }\n </div>\n }\n <!-- Full Form Mode (complex forms) -->\n @if (!IsSimpleChoice) {\n <form [formGroup]=\"FormGroup\" (ngSubmit)=\"OnSubmit()\" class=\"response-form\">\n <!-- Form Title -->\n @if (FormDefinition.title) {\n <h3 class=\"form-title\">{{ FormDefinition.title }}</h3>\n }\n <!-- Form Description -->\n @if (FormDefinition.description) {\n <p class=\"form-description\">{{ FormDefinition.description }}</p>\n }\n <!-- Questions (excludes footer choice question) -->\n @if (MainQuestions.length > 0) {\n <div class=\"form-questions\">\n @for (question of MainQuestions; track TrackByQuestionId($index, question)) {\n <mj-dynamic-form-field\n [Question]=\"question\"\n [Control]=\"GetControl(question.id)\"\n [formControlName]=\"question.id\"\n ></mj-dynamic-form-field>\n }\n </div>\n }\n <!-- Footer Actions -->\n <div class=\"form-actions\">\n <!-- Footer Choice Buttons (replaces Submit when present) -->\n @if (FooterChoiceQuestion) {\n @for (option of GetOptions(FooterChoiceQuestion); track TrackByValue($index, option)) {\n <button\n kendoButton\n type=\"button\"\n [themeColor]=\"IsFooterChoiceSelected(option.value) ? 'primary' : 'base'\"\n [disabled]=\"Disabled\"\n (click)=\"OnFooterChoiceClick(option.value)\"\n class=\"footer-choice-button\"\n [class.selected]=\"IsFooterChoiceSelected(option.value)\"\n >\n <span>\n @if (IsFooterChoiceSelected(option.value)) {\n <i class=\"fa-solid fa-check\" aria-hidden=\"true\"></i>\n } @else if (HasIcon(option)) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </span>\n </button>\n }\n }\n <!-- Standard Submit Button (when no footer choice) -->\n @if (ShowSubmitButton) {\n <button\n kendoButton\n type=\"submit\"\n [themeColor]=\"'primary'\"\n [disabled]=\"Disabled || IsSubmitting || FormGroup.invalid\"\n class=\"submit-button\"\n >\n @if (!IsSubmitting) {\n <span>\n <i class=\"fa fa-check\" aria-hidden=\"true\"></i>\n {{ SubmitLabel }}\n </span>\n }\n @if (IsSubmitting) {\n <span>\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n Submitting...\n </span>\n }\n </button>\n }\n </div>\n </form>\n }\n </div>\n}\n", styles: [".dynamic-form {\n margin-top: 16px;\n display: block;\n width: fit-content;\n max-width: 600px;\n}\n\n/* Simple Choice Mode */\n.simple-choice-buttons {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 12px;\n}\n\n.choice-button {\n padding: 8px 16px;\n font-size: 14px;\n}\n\n.choice-button i,\n.choice-button i.fa-solid {\n margin-right: 6px;\n}\n\n.choice-button.selected {\n box-shadow: 0 0 0 2px var(--mj-brand-primary);\n}\n\n/* Full Form Mode */\n.response-form {\n background: color-mix(in srgb, var(--mj-brand-primary) 3%, transparent);\n border: 2px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n border-radius: 12px;\n padding: 0;\n margin-top: 12px;\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n overflow: hidden;\n}\n\n.form-title {\n margin: 0;\n padding: 12px 16px;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-inverse);\n background: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.form-title::before {\n content: \"\\f044\";\n font-family: \"Font Awesome 6 Free\";\n font-weight: 900;\n font-size: 13px;\n opacity: 0.95;\n}\n\n.form-description {\n margin: 0;\n padding: 12px 16px;\n font-size: 13px;\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n border-bottom: 1px solid color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n font-style: italic;\n}\n\n.form-questions {\n padding: 16px;\n background: var(--mj-bg-surface-card);\n}\n\n.form-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: flex-start;\n padding: 12px 16px;\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, transparent);\n border-top: 1px solid color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.submit-button {\n min-width: 120px;\n}\n\n/* Footer choice buttons - horizontal on desktop with text wrapping */\n.footer-choice-button {\n min-width: 120px;\n max-width: 100%;\n white-space: normal;\n text-align: left;\n line-height: 1.3;\n padding: 10px 16px;\n}\n\n.footer-choice-button i.fa,\n.footer-choice-button i.fa-solid {\n margin-right: 6px;\n flex-shrink: 0;\n}\n\n.footer-choice-button.selected {\n box-shadow: 0 0 0 2px var(--mj-brand-primary);\n}\n\n.submit-button i.fa {\n margin-right: 6px;\n}\n\n/* Responsive Design */\n@media (max-width: 768px) {\n .dynamic-form {\n margin-top: 12px;\n }\n\n .response-form {\n border-radius: 10px;\n }\n\n .form-questions {\n padding: 12px;\n }\n\n .form-actions {\n padding: 10px 12px;\n }\n\n .simple-choice-buttons {\n gap: 8px;\n }\n\n .choice-button {\n padding: 10px 14px;\n font-size: 13px;\n }\n}\n\n@media (max-width: 480px) {\n .dynamic-form {\n margin-top: 10px;\n width: 100%;\n max-width: 100%;\n }\n\n .response-form {\n border-radius: 8px;\n border-width: 1.5px;\n }\n\n .form-title {\n font-size: 13px;\n padding: 10px 12px;\n }\n\n .form-description {\n font-size: 12px;\n padding: 10px 12px;\n }\n\n .form-questions {\n padding: 10px;\n }\n\n .form-actions {\n padding: 8px 10px;\n flex-direction: column;\n }\n\n .choice-button {\n width: 100%;\n min-height: 44px;\n padding: 12px 16px;\n font-size: 14px;\n }\n\n .footer-choice-button {\n width: 100%;\n min-height: 44px;\n font-size: 14px;\n }\n\n .submit-button {\n width: 100%;\n min-height: 44px;\n font-size: 13px;\n }\n}\n"], dependencies: [{ kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: i3.DynamicFormFieldComponent, selector: "mj-dynamic-form-field", inputs: ["Question", "Control"] }] });
|
|
245
|
+
}
|
|
246
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormComponent, decorators: [{
|
|
247
|
+
type: Component,
|
|
248
|
+
args: [{ standalone: false, selector: 'mj-dynamic-form', template: "@if (IsVisible) {\n <div class=\"dynamic-form\">\n <!-- Simple Choice Mode (single question, no title, buttongroup/radio) -->\n @if (IsSimpleChoice) {\n <div class=\"simple-choice-buttons\">\n @for (option of GetOptions(FormDefinition.questions[0]); track TrackByValue($index, option)) {\n <button\n kendoButton\n [themeColor]=\"IsSimpleChoiceSelected(option.value) ? 'primary' : 'base'\"\n [disabled]=\"Disabled\"\n (click)=\"OnSimpleChoiceClick(option.value)\"\n class=\"choice-button\"\n [class.selected]=\"IsSimpleChoiceSelected(option.value)\"\n >\n <span>\n @if (IsSimpleChoiceSelected(option.value)) {\n <i class=\"fa-solid fa-check\" aria-hidden=\"true\"></i>\n } @else if (HasIcon(option)) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </span>\n </button>\n }\n </div>\n }\n <!-- Full Form Mode (complex forms) -->\n @if (!IsSimpleChoice) {\n <form [formGroup]=\"FormGroup\" (ngSubmit)=\"OnSubmit()\" class=\"response-form\">\n <!-- Form Title -->\n @if (FormDefinition.title) {\n <h3 class=\"form-title\">{{ FormDefinition.title }}</h3>\n }\n <!-- Form Description -->\n @if (FormDefinition.description) {\n <p class=\"form-description\">{{ FormDefinition.description }}</p>\n }\n <!-- Questions (excludes footer choice question) -->\n @if (MainQuestions.length > 0) {\n <div class=\"form-questions\">\n @for (question of MainQuestions; track TrackByQuestionId($index, question)) {\n <mj-dynamic-form-field\n [Question]=\"question\"\n [Control]=\"GetControl(question.id)\"\n [formControlName]=\"question.id\"\n ></mj-dynamic-form-field>\n }\n </div>\n }\n <!-- Footer Actions -->\n <div class=\"form-actions\">\n <!-- Footer Choice Buttons (replaces Submit when present) -->\n @if (FooterChoiceQuestion) {\n @for (option of GetOptions(FooterChoiceQuestion); track TrackByValue($index, option)) {\n <button\n kendoButton\n type=\"button\"\n [themeColor]=\"IsFooterChoiceSelected(option.value) ? 'primary' : 'base'\"\n [disabled]=\"Disabled\"\n (click)=\"OnFooterChoiceClick(option.value)\"\n class=\"footer-choice-button\"\n [class.selected]=\"IsFooterChoiceSelected(option.value)\"\n >\n <span>\n @if (IsFooterChoiceSelected(option.value)) {\n <i class=\"fa-solid fa-check\" aria-hidden=\"true\"></i>\n } @else if (HasIcon(option)) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </span>\n </button>\n }\n }\n <!-- Standard Submit Button (when no footer choice) -->\n @if (ShowSubmitButton) {\n <button\n kendoButton\n type=\"submit\"\n [themeColor]=\"'primary'\"\n [disabled]=\"Disabled || IsSubmitting || FormGroup.invalid\"\n class=\"submit-button\"\n >\n @if (!IsSubmitting) {\n <span>\n <i class=\"fa fa-check\" aria-hidden=\"true\"></i>\n {{ SubmitLabel }}\n </span>\n }\n @if (IsSubmitting) {\n <span>\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n Submitting...\n </span>\n }\n </button>\n }\n </div>\n </form>\n }\n </div>\n}\n", styles: [".dynamic-form {\n margin-top: 16px;\n display: block;\n width: fit-content;\n max-width: 600px;\n}\n\n/* Simple Choice Mode */\n.simple-choice-buttons {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 12px;\n}\n\n.choice-button {\n padding: 8px 16px;\n font-size: 14px;\n}\n\n.choice-button i,\n.choice-button i.fa-solid {\n margin-right: 6px;\n}\n\n.choice-button.selected {\n box-shadow: 0 0 0 2px var(--mj-brand-primary);\n}\n\n/* Full Form Mode */\n.response-form {\n background: color-mix(in srgb, var(--mj-brand-primary) 3%, transparent);\n border: 2px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n border-radius: 12px;\n padding: 0;\n margin-top: 12px;\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n overflow: hidden;\n}\n\n.form-title {\n margin: 0;\n padding: 12px 16px;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-inverse);\n background: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.form-title::before {\n content: \"\\f044\";\n font-family: \"Font Awesome 6 Free\";\n font-weight: 900;\n font-size: 13px;\n opacity: 0.95;\n}\n\n.form-description {\n margin: 0;\n padding: 12px 16px;\n font-size: 13px;\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n border-bottom: 1px solid color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n font-style: italic;\n}\n\n.form-questions {\n padding: 16px;\n background: var(--mj-bg-surface-card);\n}\n\n.form-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: flex-start;\n padding: 12px 16px;\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, transparent);\n border-top: 1px solid color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.submit-button {\n min-width: 120px;\n}\n\n/* Footer choice buttons - horizontal on desktop with text wrapping */\n.footer-choice-button {\n min-width: 120px;\n max-width: 100%;\n white-space: normal;\n text-align: left;\n line-height: 1.3;\n padding: 10px 16px;\n}\n\n.footer-choice-button i.fa,\n.footer-choice-button i.fa-solid {\n margin-right: 6px;\n flex-shrink: 0;\n}\n\n.footer-choice-button.selected {\n box-shadow: 0 0 0 2px var(--mj-brand-primary);\n}\n\n.submit-button i.fa {\n margin-right: 6px;\n}\n\n/* Responsive Design */\n@media (max-width: 768px) {\n .dynamic-form {\n margin-top: 12px;\n }\n\n .response-form {\n border-radius: 10px;\n }\n\n .form-questions {\n padding: 12px;\n }\n\n .form-actions {\n padding: 10px 12px;\n }\n\n .simple-choice-buttons {\n gap: 8px;\n }\n\n .choice-button {\n padding: 10px 14px;\n font-size: 13px;\n }\n}\n\n@media (max-width: 480px) {\n .dynamic-form {\n margin-top: 10px;\n width: 100%;\n max-width: 100%;\n }\n\n .response-form {\n border-radius: 8px;\n border-width: 1.5px;\n }\n\n .form-title {\n font-size: 13px;\n padding: 10px 12px;\n }\n\n .form-description {\n font-size: 12px;\n padding: 10px 12px;\n }\n\n .form-questions {\n padding: 10px;\n }\n\n .form-actions {\n padding: 8px 10px;\n flex-direction: column;\n }\n\n .choice-button {\n width: 100%;\n min-height: 44px;\n padding: 12px 16px;\n font-size: 14px;\n }\n\n .footer-choice-button {\n width: 100%;\n min-height: 44px;\n font-size: 14px;\n }\n\n .submit-button {\n width: 100%;\n min-height: 44px;\n font-size: 13px;\n }\n}\n"] }]
|
|
249
|
+
}], propDecorators: { FormDefinition: [{
|
|
250
|
+
type: Input
|
|
251
|
+
}], Disabled: [{
|
|
252
|
+
type: Input
|
|
253
|
+
}], Visible: [{
|
|
254
|
+
type: Input
|
|
255
|
+
}], FormSubmitted: [{
|
|
256
|
+
type: Output
|
|
257
|
+
}] } });
|
|
258
|
+
//# sourceMappingURL=dynamic-form.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-form.component.js","sourceRoot":"","sources":["../../../src/lib/dynamic-form/dynamic-form.component.ts","../../../src/lib/dynamic-form/dynamic-form.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Dynamic Form Component\n *\n * Top-level container that renders a complete form from an `AgentResponseForm`\n * schema definition. Automatically builds a reactive `FormGroup`, handles\n * validation, and emits structured response data on submission.\n *\n * Supports two rendering modes:\n * - **Simple choice mode**: Single buttongroup/radio question with no title —\n * renders as standalone buttons for quick one-click responses.\n * - **Full form mode**: Title bar, description, multiple fields, and an action\n * footer with either a Submit button or choice buttons.\n *\n * @example Basic usage\n * ```html\n * <mj-dynamic-form\n * [FormDefinition]=\"responseForm\"\n * [Disabled]=\"isSaving\"\n * (FormSubmitted)=\"onFormSubmitted($event)\">\n * </mj-dynamic-form>\n * ```\n *\n * @example With visibility control\n * ```html\n * <mj-dynamic-form\n * [FormDefinition]=\"responseForm\"\n * [Visible]=\"isLastMessage && isOwner\"\n * (FormSubmitted)=\"onFormSubmitted($event)\">\n * </mj-dynamic-form>\n * ```\n *\n * @module @memberjunction/ng-forms\n */\n\nimport { Component, Input, Output, EventEmitter, OnInit, ChangeDetectorRef, inject } from '@angular/core';\nimport { FormGroup, FormControl, Validators } from '@angular/forms';\nimport { AgentResponseForm, FormQuestion, FormOption } from '@memberjunction/ai-core-plus';\n\n@Component({\n standalone: false,\n selector: 'mj-dynamic-form',\n templateUrl: './dynamic-form.component.html',\n styleUrls: ['./dynamic-form.component.css']\n})\nexport class DynamicFormComponent implements OnInit {\n /**\n * The form schema that drives rendering.\n * Contains title, description, questions array, and optional submit label.\n */\n @Input() FormDefinition!: AgentResponseForm;\n\n /**\n * Whether the form is disabled (e.g. while saving).\n * Disables all inputs and buttons.\n */\n @Input() Disabled = false;\n\n /**\n * Controls overall visibility. When `false`, the component renders nothing.\n * Defaults to `true` so the form is always shown unless explicitly hidden.\n */\n @Input() Visible = true;\n\n /**\n * Emitted when the user submits the form. The payload is a key-value map\n * where keys are question IDs and values are the user's responses.\n */\n @Output() FormSubmitted = new EventEmitter<Record<string, string | number | boolean | Date>>();\n\n /** The reactive form group built from the form definition */\n public FormGroup!: FormGroup;\n\n /** Whether a submission is in progress */\n public IsSubmitting = false;\n\n private cdr = inject(ChangeDetectorRef);\n\n ngOnInit(): void {\n this.buildFormGroup();\n }\n\n /**\n * Whether this form renders as a simple choice (single buttongroup/radio, no title).\n */\n public get IsSimpleChoice(): boolean {\n if (!this.FormDefinition) return false;\n return (\n this.FormDefinition.questions.length === 1 &&\n !this.FormDefinition.title &&\n this.isChoiceQuestion(this.FormDefinition.questions[0])\n );\n }\n\n /**\n * The choice question that should render in the footer (if present).\n * When a form has a buttongroup/radio question, those buttons replace\n * the standard Submit button in the footer.\n */\n public get FooterChoiceQuestion(): FormQuestion | null {\n if (!this.FormDefinition || this.IsSimpleChoice) return null;\n const choiceQuestions = this.FormDefinition.questions.filter(q => this.isChoiceQuestion(q));\n return choiceQuestions.length > 0 ? choiceQuestions[choiceQuestions.length - 1] : null;\n }\n\n /**\n * Questions rendered in the main form area (excludes footer choice question).\n */\n public get MainQuestions(): FormQuestion[] {\n if (!this.FormDefinition) return [];\n const footerChoice = this.FooterChoiceQuestion;\n if (!footerChoice) return this.FormDefinition.questions;\n return this.FormDefinition.questions.filter(q => q.id !== footerChoice.id);\n }\n\n /**\n * Whether to show the standard Submit button (true when no footer choice question).\n */\n public get ShowSubmitButton(): boolean {\n return !this.FooterChoiceQuestion;\n }\n\n /**\n * Whether the form should be visible (has questions and Visible is true).\n */\n public get IsVisible(): boolean {\n return (\n this.Visible &&\n !!this.FormDefinition &&\n this.FormDefinition.questions.length > 0\n );\n }\n\n /**\n * Text for the submit button. Falls back to \"Submit\" if not specified.\n */\n public get SubmitLabel(): string {\n return this.FormDefinition?.submitLabel || 'Submit';\n }\n\n /** Get the FormControl for a specific question by ID */\n public GetControl(questionId: string): FormControl {\n return this.FormGroup.get(questionId) as FormControl;\n }\n\n /** Handle simple choice button click (single-question forms) */\n public OnSimpleChoiceClick(value: string | number | boolean): void {\n if (!this.Disabled) {\n const question = this.FormDefinition.questions[0];\n // Update the FormControl so external readers (e.g. collectFormData) can access the value\n this.FormGroup.get(question.id)?.setValue(value);\n this.FormSubmitted.emit({ [question.id]: value });\n }\n }\n\n /** Whether a given option value is the currently selected simple choice */\n public IsSimpleChoiceSelected(value: string | number | boolean): boolean {\n if (!this.IsSimpleChoice) return false;\n const question = this.FormDefinition.questions[0];\n const control = this.FormGroup.get(question.id);\n return control?.value === value;\n }\n\n /** Handle footer choice button click — selects the option and emits the form data */\n public OnFooterChoiceClick(value: string | number | boolean): void {\n if (!this.Disabled && this.FooterChoiceQuestion) {\n const mainControlKeys = this.MainQuestions.map(q => q.id);\n let hasInvalidFields = false;\n\n for (const key of mainControlKeys) {\n const control = this.FormGroup.get(key);\n if (control?.invalid) {\n control.markAsTouched();\n hasInvalidFields = true;\n }\n }\n\n if (hasInvalidFields) return;\n\n // Update the FormControl so external readers (e.g. collectFormData) can access the value\n this.FormGroup.get(this.FooterChoiceQuestion.id)?.setValue(value);\n const formData = { ...this.FormGroup.value };\n this.FormSubmitted.emit(formData);\n }\n }\n\n /** Whether a given option value is the currently selected footer choice */\n public IsFooterChoiceSelected(value: string | number | boolean): boolean {\n if (!this.FooterChoiceQuestion) return false;\n const control = this.FormGroup.get(this.FooterChoiceQuestion.id);\n return control?.value === value;\n }\n\n /** Handle full form submission */\n public OnSubmit(): void {\n if (this.FormGroup.invalid) {\n Object.keys(this.FormGroup.controls).forEach(key => {\n this.FormGroup.controls[key].markAsTouched();\n });\n return;\n }\n\n if (!this.Disabled && !this.IsSubmitting) {\n this.IsSubmitting = true;\n this.FormSubmitted.emit(this.FormGroup.value);\n\n // Reset submitting state after a short delay — the parent container\n // is responsible for hiding the form or handling the response\n setTimeout(() => {\n this.IsSubmitting = false;\n this.cdr.detectChanges();\n }, 1000);\n }\n }\n\n /** Get options for a choice question */\n public GetOptions(question: FormQuestion): FormOption[] {\n if (typeof question.type === 'object' && 'options' in question.type) {\n return question.type.options;\n }\n return [];\n }\n\n /** Check if an option has an icon */\n public HasIcon(option: FormOption): boolean {\n return !!option?.icon;\n }\n\n /** Track-by function for questions */\n public TrackByQuestionId(_index: number, question: FormQuestion): string {\n return question.id;\n }\n\n /** Track-by function for options */\n public TrackByValue(_index: number, option: FormOption): string | number | boolean {\n return option.value;\n }\n\n private isChoiceQuestion(question: FormQuestion): boolean {\n const type = typeof question.type === 'string' ? question.type : question.type.type;\n return ['buttongroup', 'radio'].includes(type);\n }\n\n private buildFormGroup(): void {\n const controls: Record<string, FormControl> = {};\n\n for (const question of this.FormDefinition.questions) {\n const validators = [];\n if (question.required) {\n validators.push(Validators.required);\n }\n\n const questionType = typeof question.type === 'string' ? question.type : question.type.type;\n if (questionType === 'email') {\n validators.push(Validators.email);\n }\n\n if (['number', 'currency'].includes(questionType) && typeof question.type === 'object') {\n if ('min' in question.type && question.type.min != null) {\n validators.push(Validators.min(question.type.min as number));\n }\n if ('max' in question.type && question.type.max != null) {\n validators.push(Validators.max(question.type.max as number));\n }\n }\n\n let initialValue = question.defaultValue;\n if (!initialValue) {\n if (questionType === 'checkbox' && typeof question.type === 'object' && 'multiple' in question.type && question.type.multiple) {\n initialValue = [];\n } else {\n initialValue = null;\n }\n }\n\n controls[question.id] = new FormControl(initialValue, validators);\n }\n\n this.FormGroup = new FormGroup(controls);\n }\n}\n","@if (IsVisible) {\n <div class=\"dynamic-form\">\n <!-- Simple Choice Mode (single question, no title, buttongroup/radio) -->\n @if (IsSimpleChoice) {\n <div class=\"simple-choice-buttons\">\n @for (option of GetOptions(FormDefinition.questions[0]); track TrackByValue($index, option)) {\n <button\n kendoButton\n [themeColor]=\"IsSimpleChoiceSelected(option.value) ? 'primary' : 'base'\"\n [disabled]=\"Disabled\"\n (click)=\"OnSimpleChoiceClick(option.value)\"\n class=\"choice-button\"\n [class.selected]=\"IsSimpleChoiceSelected(option.value)\"\n >\n <span>\n @if (IsSimpleChoiceSelected(option.value)) {\n <i class=\"fa-solid fa-check\" aria-hidden=\"true\"></i>\n } @else if (HasIcon(option)) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </span>\n </button>\n }\n </div>\n }\n <!-- Full Form Mode (complex forms) -->\n @if (!IsSimpleChoice) {\n <form [formGroup]=\"FormGroup\" (ngSubmit)=\"OnSubmit()\" class=\"response-form\">\n <!-- Form Title -->\n @if (FormDefinition.title) {\n <h3 class=\"form-title\">{{ FormDefinition.title }}</h3>\n }\n <!-- Form Description -->\n @if (FormDefinition.description) {\n <p class=\"form-description\">{{ FormDefinition.description }}</p>\n }\n <!-- Questions (excludes footer choice question) -->\n @if (MainQuestions.length > 0) {\n <div class=\"form-questions\">\n @for (question of MainQuestions; track TrackByQuestionId($index, question)) {\n <mj-dynamic-form-field\n [Question]=\"question\"\n [Control]=\"GetControl(question.id)\"\n [formControlName]=\"question.id\"\n ></mj-dynamic-form-field>\n }\n </div>\n }\n <!-- Footer Actions -->\n <div class=\"form-actions\">\n <!-- Footer Choice Buttons (replaces Submit when present) -->\n @if (FooterChoiceQuestion) {\n @for (option of GetOptions(FooterChoiceQuestion); track TrackByValue($index, option)) {\n <button\n kendoButton\n type=\"button\"\n [themeColor]=\"IsFooterChoiceSelected(option.value) ? 'primary' : 'base'\"\n [disabled]=\"Disabled\"\n (click)=\"OnFooterChoiceClick(option.value)\"\n class=\"footer-choice-button\"\n [class.selected]=\"IsFooterChoiceSelected(option.value)\"\n >\n <span>\n @if (IsFooterChoiceSelected(option.value)) {\n <i class=\"fa-solid fa-check\" aria-hidden=\"true\"></i>\n } @else if (HasIcon(option)) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </span>\n </button>\n }\n }\n <!-- Standard Submit Button (when no footer choice) -->\n @if (ShowSubmitButton) {\n <button\n kendoButton\n type=\"submit\"\n [themeColor]=\"'primary'\"\n [disabled]=\"Disabled || IsSubmitting || FormGroup.invalid\"\n class=\"submit-button\"\n >\n @if (!IsSubmitting) {\n <span>\n <i class=\"fa fa-check\" aria-hidden=\"true\"></i>\n {{ SubmitLabel }}\n </span>\n }\n @if (IsSubmitting) {\n <span>\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n Submitting...\n </span>\n }\n </button>\n }\n </div>\n </form>\n }\n </div>\n}\n"]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Dynamic Form Field Component
|
|
3
|
+
*
|
|
4
|
+
* Renders a single form field based on a `FormQuestion` definition from
|
|
5
|
+
* `@memberjunction/ai-core-plus`. Supports 14+ input types including text,
|
|
6
|
+
* textarea, email, number, currency, date, datetime, time, daterange,
|
|
7
|
+
* buttongroup, radio, dropdown, checkbox, and slider.
|
|
8
|
+
*
|
|
9
|
+
* Implements `ControlValueAccessor` for seamless integration with Angular
|
|
10
|
+
* Reactive Forms. Width is automatically sized based on field type or an
|
|
11
|
+
* explicit `widthHint` on the question definition.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```html
|
|
15
|
+
* <mj-dynamic-form-field
|
|
16
|
+
* [Question]="question"
|
|
17
|
+
* [Control]="formGroup.get(question.id)"
|
|
18
|
+
* [formControlName]="question.id">
|
|
19
|
+
* </mj-dynamic-form-field>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @module @memberjunction/ng-forms
|
|
23
|
+
*/
|
|
24
|
+
import { OnInit } from '@angular/core';
|
|
25
|
+
import { ControlValueAccessor, FormControl } from '@angular/forms';
|
|
26
|
+
import { FormQuestion, FormOption } from '@memberjunction/ai-core-plus';
|
|
27
|
+
import * as i0 from "@angular/core";
|
|
28
|
+
export declare class DynamicFormFieldComponent implements ControlValueAccessor, OnInit {
|
|
29
|
+
/**
|
|
30
|
+
* The question definition that drives this field's rendering.
|
|
31
|
+
* Determines the input type, label, validation, options, and layout.
|
|
32
|
+
*/
|
|
33
|
+
Question: FormQuestion;
|
|
34
|
+
/**
|
|
35
|
+
* The reactive FormControl bound to this field.
|
|
36
|
+
* Used for validation state display and dropdown binding.
|
|
37
|
+
*/
|
|
38
|
+
Control: FormControl;
|
|
39
|
+
/** Current field value (managed by ControlValueAccessor) */
|
|
40
|
+
Value: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null;
|
|
41
|
+
/** Whether this field is disabled */
|
|
42
|
+
Disabled: boolean;
|
|
43
|
+
private onChange;
|
|
44
|
+
private onTouched;
|
|
45
|
+
ngOnInit(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Resolved question type string (handles both simple string and object types).
|
|
48
|
+
*/
|
|
49
|
+
get QuestionType(): string;
|
|
50
|
+
/**
|
|
51
|
+
* Whether this is a choice-based question (buttongroup, radio, dropdown, checkbox).
|
|
52
|
+
*/
|
|
53
|
+
get IsChoiceQuestion(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Available options for choice-type questions.
|
|
56
|
+
*/
|
|
57
|
+
get Options(): FormOption[];
|
|
58
|
+
/**
|
|
59
|
+
* Whether multiple selections are allowed (for checkbox type).
|
|
60
|
+
*/
|
|
61
|
+
get AllowMultiple(): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* CSS class controlling the field width based on `widthHint` or intelligent defaults.
|
|
64
|
+
*
|
|
65
|
+
* Width mapping:
|
|
66
|
+
* - `'narrow'` (~200px): numbers, dates, times
|
|
67
|
+
* - `'medium'` (~450px): text inputs (default)
|
|
68
|
+
* - `'wide'` (100%): emails, buttongroup, radio, checkbox
|
|
69
|
+
* - `'full'` (100%): textarea
|
|
70
|
+
* - `'auto'` (150–350px): dropdowns
|
|
71
|
+
*/
|
|
72
|
+
get WidthClass(): string;
|
|
73
|
+
/** Placeholder text for text-based inputs */
|
|
74
|
+
get Placeholder(): string | undefined;
|
|
75
|
+
/** Minimum value for numeric inputs */
|
|
76
|
+
get Min(): number | undefined;
|
|
77
|
+
/** Maximum value for numeric inputs */
|
|
78
|
+
get Max(): number | undefined;
|
|
79
|
+
/** Step increment for numeric inputs */
|
|
80
|
+
get Step(): number | undefined;
|
|
81
|
+
/** Prefix string for currency inputs (e.g. "$") */
|
|
82
|
+
get Prefix(): string | undefined;
|
|
83
|
+
/** Suffix string for currency/slider inputs (e.g. "USD", "%") */
|
|
84
|
+
get Suffix(): string | undefined;
|
|
85
|
+
/** Handle value changes from any input type */
|
|
86
|
+
OnValueChange(newValue: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null): void;
|
|
87
|
+
/** Toggle a checkbox option (supports single and multiple selection) */
|
|
88
|
+
ToggleCheckbox(option: FormOption): void;
|
|
89
|
+
/** Check whether a checkbox option is currently selected */
|
|
90
|
+
IsChecked(option: FormOption): boolean;
|
|
91
|
+
/** Get slider configuration with defaults */
|
|
92
|
+
GetSliderConfig(): {
|
|
93
|
+
min: number;
|
|
94
|
+
max: number;
|
|
95
|
+
step?: number;
|
|
96
|
+
suffix?: string;
|
|
97
|
+
};
|
|
98
|
+
/** Handle date range start date change */
|
|
99
|
+
OnDateRangeStartChange(value: Date): void;
|
|
100
|
+
/** Handle date range end date change */
|
|
101
|
+
OnDateRangeEndChange(value: Date): void;
|
|
102
|
+
/** Track-by function for options lists */
|
|
103
|
+
TrackByValue(_index: number, option: FormOption): string | number | boolean;
|
|
104
|
+
writeValue(value: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null): void;
|
|
105
|
+
registerOnChange(fn: (value: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null) => void): void;
|
|
106
|
+
registerOnTouched(fn: () => void): void;
|
|
107
|
+
setDisabledState(isDisabled: boolean): void;
|
|
108
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DynamicFormFieldComponent, never>;
|
|
109
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<DynamicFormFieldComponent, "mj-dynamic-form-field", never, { "Question": { "alias": "Question"; "required": false; }; "Control": { "alias": "Control"; "required": false; }; }, {}, never, never, false, never>;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=dynamic-form-field.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-form-field.component.d.ts","sourceRoot":"","sources":["../../../src/lib/dynamic-form-field/dynamic-form-field.component.ts"],"names":[],"mappings":"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"}
|