@memberjunction/ng-forms 6.1.0-edge.4 → 6.1.0-edge.6

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.
@@ -240,12 +240,12 @@ export class DynamicFormComponent {
240
240
  }
241
241
  this.FormGroup = new FormGroup(controls);
242
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 mjButton\n [variant]=\"IsSimpleChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"button\"\n [variant]=\"IsFooterChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"submit\"\n variant=\"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: "directive", type: i2.MJButtonDirective, selector: "button[mjButton], a[mjButton]", inputs: ["variant", "size", "toggleable", "selected", "ariaLabel"], outputs: ["selectedChange"] }, { kind: "component", type: i3.DynamicFormFieldComponent, selector: "mj-dynamic-form-field", inputs: ["Question", "Control"] }] });
243
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
244
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", 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 mjButton\n [variant]=\"IsSimpleChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"button\"\n [variant]=\"IsFooterChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"submit\"\n variant=\"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 /* Sizes to content, so a short form stays compact \u2014 but agent-authored labels are\n often sentence-length, so give the card enough room before its contents have to wrap. */\n width: fit-content;\n min-width: 0;\n max-width: 720px;\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: "directive", type: i2.MJButtonDirective, selector: "button[mjButton], a[mjButton]", inputs: ["variant", "size", "toggleable", "selected", "ariaLabel"], outputs: ["selectedChange"] }, { kind: "component", type: i3.DynamicFormFieldComponent, selector: "mj-dynamic-form-field", inputs: ["Question", "Control"] }] });
245
245
  }
246
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormComponent, decorators: [{
246
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormComponent, decorators: [{
247
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 mjButton\n [variant]=\"IsSimpleChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"button\"\n [variant]=\"IsFooterChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"submit\"\n variant=\"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"] }]
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 mjButton\n [variant]=\"IsSimpleChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"button\"\n [variant]=\"IsFooterChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"submit\"\n variant=\"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 /* Sizes to content, so a short form stays compact \u2014 but agent-authored labels are\n often sentence-length, so give the card enough room before its contents have to wrap. */\n width: fit-content;\n min-width: 0;\n max-width: 720px;\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
249
  }], propDecorators: { FormDefinition: [{
250
250
  type: Input
251
251
  }], Disabled: [{
@@ -1 +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 mjButton\n [variant]=\"IsSimpleChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"button\"\n [variant]=\"IsFooterChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"submit\"\n variant=\"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"]}
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 mjButton\n [variant]=\"IsSimpleChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"button\"\n [variant]=\"IsFooterChoiceSelected(option.value) ? 'primary' : 'secondary'\"\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 mjButton\n type=\"submit\"\n variant=\"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"]}
@@ -234,16 +234,16 @@ export class DynamicFormFieldComponent {
234
234
  setDisabledState(isDisabled) {
235
235
  this.Disabled = isDisabled;
236
236
  }
237
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
238
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: DynamicFormFieldComponent, isStandalone: false, selector: "mj-dynamic-form-field", inputs: { Question: "Question", Control: "Control" }, providers: [
237
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
238
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: DynamicFormFieldComponent, isStandalone: false, selector: "mj-dynamic-form-field", inputs: { Question: "Question", Control: "Control" }, providers: [
239
239
  {
240
240
  provide: NG_VALUE_ACCESSOR,
241
241
  useExisting: forwardRef(() => DynamicFormFieldComponent),
242
242
  multi: true
243
243
  }
244
- ], ngImport: i0, template: "<div class=\"form-question\" [class.required]=\"Question.required\">\n <!-- Label -->\n @if (Question.label) {\n <label class=\"question-label\">\n {{ Question.label }}\n @if (Question.required) {\n <span class=\"required-indicator\">*</span>\n }\n </label>\n }\n\n <!-- Help Text -->\n @if (Question.helpText) {\n <div class=\"help-text\">\n {{ Question.helpText }}\n </div>\n }\n\n <!-- Input Container with width class -->\n <div class=\"input-container\" [ngClass]=\"WidthClass\">\n\n <!-- Text Input -->\n @if (QuestionType === 'text') {\n <input\n type=\"text\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Email Input -->\n @if (QuestionType === 'email') {\n <input\n type=\"email\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Textarea (markdown mode: rendered preview by default, Edit toggles the raw editor) -->\n @if (QuestionType === 'textarea') {\n @if (IsMarkdown) {\n <div class=\"markdown-field\" [class.editing]=\"MarkdownEditing\">\n <div class=\"markdown-field-toolbar\">\n <span class=\"markdown-field-hint\">\n <i class=\"fa-brands fa-markdown\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Editing Markdown' : 'Formatted preview' }}\n </span>\n <button\n type=\"button\"\n class=\"markdown-field-toggle\"\n [disabled]=\"Disabled\"\n (click)=\"ToggleMarkdownEditing()\"\n [title]=\"MarkdownEditing ? 'Show the formatted preview' : 'Edit the raw Markdown'\"\n >\n <i class=\"fa-solid\" [ngClass]=\"MarkdownEditing ? 'fa-eye' : 'fa-pen'\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Preview' : 'Edit' }}\n </button>\n </div>\n @if (!MarkdownEditing) {\n <div class=\"markdown-field-preview\">\n <mj-markdown [data]=\"MarkdownValue\"></mj-markdown>\n </div>\n }\n @if (MarkdownEditing) {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"10\"\n ></textarea>\n }\n </div>\n } @else {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"5\"\n ></textarea>\n }\n }\n\n <!-- Number Input -->\n @if (QuestionType === 'number') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 1\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Currency Input -->\n @if (QuestionType === 'currency') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 0.01\"\n [Decimals]=\"2\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Date Picker -->\n @if (QuestionType === 'date') {\n <mj-datepicker\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-datepicker>\n }\n\n <!-- DateTime Picker -->\n @if (QuestionType === 'datetime') {\n <input\n type=\"datetime-local\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Button Group -->\n @if (QuestionType === 'buttongroup') {\n <div class=\"mj-buttongroup question-buttongroup\">\n @for (option of Options; track TrackByValue($index, option)) {\n <button\n mjButton\n [toggleable]=\"true\"\n [selected]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (click)=\"OnValueChange(option.value)\"\n class=\"buttongroup-option\"\n >\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </button>\n }\n </div>\n }\n\n <!-- Radio Buttons -->\n @if (QuestionType === 'radio') {\n <div class=\"question-radio-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"radio-option\">\n <input\n type=\"radio\"\n class=\"mj-radio\"\n [name]=\"Question.id\"\n [value]=\"option.value\"\n [checked]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange(option.value)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"radio-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n\n <!-- Dropdown -->\n @if (QuestionType === 'dropdown') {\n <select\n [formControl]=\"Control\"\n [disabled]=\"Disabled\"\n class=\"question-dropdown\"\n >\n <option value=\"\">Select an option...</option>\n @for (option of Options; track TrackByValue($index, option)) {\n <option [value]=\"option.value\">\n {{ option.label }}\n </option>\n }\n </select>\n }\n\n <!-- Slider -->\n @if (QuestionType === 'slider') {\n <div class=\"question-slider-container\">\n <input\n type=\"range\"\n class=\"mj-slider question-slider\"\n [min]=\"GetSliderConfig().min\"\n [max]=\"GetSliderConfig().max\"\n [step]=\"GetSliderConfig().step || 1\"\n [value]=\"$any(Value) || GetSliderConfig().min\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange(+$any($event.target).value)\"\n />\n <div class=\"slider-value\">\n {{ Value || GetSliderConfig().min }}{{ GetSliderConfig().suffix || '' }}\n </div>\n </div>\n }\n\n <!-- Date Range -->\n @if (QuestionType === 'daterange') {\n <div class=\"question-daterange\">\n <mj-datepicker\n [ngModel]=\"$any(Value)?.start\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeStartChange($any($event))\"\n Placeholder=\"Start Date\"\n class=\"daterange-start\"\n ></mj-datepicker>\n <span class=\"daterange-separator\">to</span>\n <mj-datepicker\n [ngModel]=\"$any(Value)?.end\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeEndChange($any($event))\"\n Placeholder=\"End Date\"\n class=\"daterange-end\"\n ></mj-datepicker>\n </div>\n }\n\n <!-- Time Picker -->\n @if (QuestionType === 'time') {\n <input\n type=\"time\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Checkboxes -->\n @if (QuestionType === 'checkbox') {\n <div class=\"question-checkbox-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"checkbox-option\">\n <input\n type=\"checkbox\"\n class=\"mj-checkbox\"\n [checked]=\"IsChecked(option)\"\n [disabled]=\"Disabled\"\n (change)=\"ToggleCheckbox(option)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"checkbox-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n </div><!-- end input-container -->\n\n <!-- Validation Error -->\n @if (Control.invalid && Control.touched) {\n <div class=\"validation-error\">\n @if (Control.errors?.['required']) {\n <span>This field is required</span>\n }\n @if (Control.errors?.['email']) {\n <span>Please enter a valid email address</span>\n }\n @if (Control.errors?.['min']) {\n <span>Value must be at least {{ Min }}</span>\n }\n @if (Control.errors?.['max']) {\n <span>Value must be at most {{ Max }}</span>\n }\n </div>\n }\n</div>\n", styles: [".form-question {\n margin-bottom: 16px;\n}\n\n.question-label {\n display: block;\n font-weight: 500;\n margin-bottom: 6px;\n color: var(--mj-text-primary);\n font-size: 14px;\n}\n\n.required-indicator {\n color: var(--mj-status-error);\n margin-left: 2px;\n}\n\n.help-text {\n font-size: 12px;\n color: var(--mj-text-secondary);\n margin-bottom: 8px;\n font-style: italic;\n}\n\n/* Default widths - controlled by input-container width classes */\n.question-input,\n.question-textarea,\n.question-dropdown {\n width: 100%;\n max-width: 100%;\n}\n\n.question-dropdown {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 14px;\n font-family: inherit;\n background-color: var(--mj-bg-surface);\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 9L1 4h10z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n background-size: 12px;\n padding-right: 32px;\n cursor: pointer;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n color: var(--mj-text-primary);\n}\n\n.question-dropdown:hover {\n border-color: var(--mj-border-strong);\n}\n\n.question-dropdown:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.question-dropdown:disabled {\n background-color: var(--mj-bg-surface-sunken);\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.question-textarea {\n resize: vertical;\n min-height: 100px;\n}\n\n.question-buttongroup {\n display: flex;\n flex-wrap: nowrap;\n gap: 8px;\n overflow-x: auto;\n overflow-y: hidden;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: thin;\n scrollbar-color: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent) transparent;\n}\n\n.question-buttongroup::-webkit-scrollbar {\n height: 6px;\n}\n\n.question-buttongroup::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.question-buttongroup::-webkit-scrollbar-thumb {\n background: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n border-radius: 3px;\n}\n\n.question-buttongroup::-webkit-scrollbar-thumb:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 50%, transparent);\n}\n\n.buttongroup-option {\n flex: 0 0 auto;\n white-space: nowrap;\n}\n\n.buttongroup-option i {\n margin-right: 6px;\n}\n\n.question-radio-group,\n.question-checkbox-group {\n display: flex;\n flex-wrap: wrap;\n gap: 12px 16px;\n}\n\n.radio-option,\n.checkbox-option {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 120px;\n flex: 0 0 auto;\n}\n\n.radio-label,\n.checkbox-label {\n cursor: pointer;\n user-select: none;\n margin: 0;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.radio-label i,\n.checkbox-label i {\n color: var(--mj-text-secondary);\n}\n\n.validation-error {\n color: var(--mj-status-error);\n font-size: 12px;\n margin-top: 4px;\n}\n\n/* Required field styling */\n.form-question.required .question-label {\n font-weight: 600;\n}\n\n/* Slider styling */\n.question-slider-container {\n width: 100%;\n}\n\n.question-slider {\n width: 100%;\n margin-bottom: 8px;\n}\n\n.slider-value {\n text-align: center;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-brand-primary);\n padding: 4px 0;\n}\n\n/* Date range styling */\n.question-daterange {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.daterange-start,\n.daterange-end {\n flex: 1;\n min-width: 150px;\n}\n\n.daterange-separator {\n font-weight: 500;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n}\n\n/* Mobile adjustments for date range */\n@media (max-width: 480px) {\n .question-daterange {\n flex-direction: column;\n align-items: stretch;\n }\n\n .daterange-separator {\n text-align: center;\n padding: 4px 0;\n }\n}\n\n/* ===== Semantic Width Classes ===== */\n/* Controls field width based on expected content length */\n\n.input-container {\n display: block;\n}\n\n/* Narrow fields - for numbers, dates, times, short codes */\n.input-container.width-narrow {\n max-width: 200px;\n}\n\n.input-container.width-narrow .question-input,\n.input-container.width-narrow .question-dropdown {\n width: 100%;\n}\n\n/* Medium fields - for names, cities, phone numbers (DEFAULT) */\n.input-container.width-medium {\n max-width: 450px;\n}\n\n.input-container.width-medium .question-input,\n.input-container.width-medium .question-dropdown {\n width: 100%;\n}\n\n/* Wide fields - for emails, URLs, long text */\n.input-container.width-wide {\n max-width: 100%;\n}\n\n.input-container.width-wide .question-input,\n.input-container.width-wide .question-dropdown {\n width: 100%;\n}\n\n/* Full width - for textarea, search fields */\n.input-container.width-full {\n max-width: 100%;\n}\n\n.input-container.width-full .question-input,\n.input-container.width-full .question-textarea,\n.input-container.width-full .question-dropdown {\n width: 100%;\n}\n\n/* Auto-width - for dropdowns, radio, checkbox (sizes to content) */\n.input-container.width-auto {\n max-width: 350px;\n min-width: 150px;\n width: auto;\n}\n\n.input-container.width-auto .question-dropdown {\n width: 100%;\n}\n\n/* Responsive adjustments for smaller screens */\n@media (max-width: 600px) {\n .input-container.width-narrow,\n .input-container.width-medium,\n .input-container.width-wide,\n .input-container.width-auto {\n max-width: 100%;\n }\n}\n\n/* \u2500\u2500 Markdown textarea (question.type.markdown) \u2014 rendered preview + Edit toggle \u2500\u2500 */\n.markdown-field {\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n.markdown-field-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 6px 10px;\n background: var(--mj-bg-surface-sunken);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.markdown-field-hint {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-muted);\n}\n.markdown-field-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font: inherit;\n font-size: 11.5px;\n font-weight: 600;\n padding: 4px 10px;\n border-radius: 7px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: color 0.15s ease, border-color 0.15s ease;\n}\n.markdown-field-toggle:hover:not(:disabled) {\n color: var(--mj-brand-primary);\n border-color: color-mix(in srgb, var(--mj-brand-primary) 45%, var(--mj-border-default));\n}\n.markdown-field-toggle:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.markdown-field-preview {\n padding: 12px 14px;\n max-height: 420px;\n overflow-y: auto;\n font-size: 13.5px;\n line-height: 1.6;\n color: var(--mj-text-primary);\n}\n/* the raw editor inside the panel loses its own border (the panel provides it) */\n.markdown-field .question-textarea {\n border: none;\n border-radius: 0;\n width: 100%;\n}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i3.MJButtonDirective, selector: "button[mjButton], a[mjButton]", inputs: ["variant", "size", "toggleable", "selected", "ariaLabel"], outputs: ["selectedChange"] }, { kind: "component", type: i3.MJNumericInputComponent, selector: "mj-numeric-input", inputs: ["Min", "Max", "Step", "Format", "Decimals", "Disabled", "Placeholder"] }, { kind: "component", type: i3.MJDatepickerComponent, selector: "mj-datepicker", inputs: ["Min", "Max", "Format", "Placeholder", "Disabled"], outputs: ["ValueChange"] }, { kind: "component", type: i4.MarkdownComponent, selector: "mj-markdown", inputs: ["data", "enableHighlight", "enableMermaid", "enableCodeCopy", "enableCollapsibleHeadings", "collapsibleHeadingLevel", "collapsibleDefaultExpanded", "autoExpandLevels", "enableAlerts", "enableSmartypants", "enableSvgRenderer", "enableHtml", "enableJavaScript", "enableHeadingIds", "headingIdPrefix", "enableLineNumbers", "containerClass", "mermaidTheme", "sanitize"], outputs: ["rendered", "headingClick", "codeCopied"] }] });
244
+ ], ngImport: i0, template: "<div class=\"form-question\" [class.required]=\"Question.required\">\n <!-- Label -->\n @if (Question.label) {\n <label class=\"question-label\">\n {{ Question.label }}\n @if (Question.required) {\n <span class=\"required-indicator\">*</span>\n }\n </label>\n }\n\n <!-- Help Text -->\n @if (Question.helpText) {\n <div class=\"help-text\">\n {{ Question.helpText }}\n </div>\n }\n\n <!-- Input Container with width class -->\n <div class=\"input-container\" [ngClass]=\"WidthClass\">\n\n <!-- Text Input -->\n @if (QuestionType === 'text') {\n <input\n type=\"text\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Email Input -->\n @if (QuestionType === 'email') {\n <input\n type=\"email\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Textarea (markdown mode: rendered preview by default, Edit toggles the raw editor) -->\n @if (QuestionType === 'textarea') {\n @if (IsMarkdown) {\n <div class=\"markdown-field\" [class.editing]=\"MarkdownEditing\">\n <div class=\"markdown-field-toolbar\">\n <span class=\"markdown-field-hint\">\n <i class=\"fa-brands fa-markdown\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Editing Markdown' : 'Formatted preview' }}\n </span>\n <button\n type=\"button\"\n class=\"markdown-field-toggle\"\n [disabled]=\"Disabled\"\n (click)=\"ToggleMarkdownEditing()\"\n [title]=\"MarkdownEditing ? 'Show the formatted preview' : 'Edit the raw Markdown'\"\n >\n <i class=\"fa-solid\" [ngClass]=\"MarkdownEditing ? 'fa-eye' : 'fa-pen'\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Preview' : 'Edit' }}\n </button>\n </div>\n @if (!MarkdownEditing) {\n <div class=\"markdown-field-preview\">\n <mj-markdown [data]=\"MarkdownValue\"></mj-markdown>\n </div>\n }\n @if (MarkdownEditing) {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"10\"\n ></textarea>\n }\n </div>\n } @else {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"5\"\n ></textarea>\n }\n }\n\n <!-- Number Input -->\n @if (QuestionType === 'number') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 1\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Currency Input -->\n @if (QuestionType === 'currency') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 0.01\"\n [Decimals]=\"2\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Date Picker -->\n @if (QuestionType === 'date') {\n <mj-datepicker\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-datepicker>\n }\n\n <!-- DateTime Picker -->\n @if (QuestionType === 'datetime') {\n <input\n type=\"datetime-local\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Button Group -->\n @if (QuestionType === 'buttongroup') {\n <div class=\"mj-buttongroup question-buttongroup\">\n @for (option of Options; track TrackByValue($index, option)) {\n <button\n mjButton\n [toggleable]=\"true\"\n [selected]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (click)=\"OnValueChange(option.value)\"\n class=\"buttongroup-option\"\n >\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </button>\n }\n </div>\n }\n\n <!-- Radio Buttons -->\n @if (QuestionType === 'radio') {\n <div class=\"question-radio-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"radio-option\">\n <input\n type=\"radio\"\n class=\"mj-radio\"\n [name]=\"Question.id\"\n [value]=\"option.value\"\n [checked]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange(option.value)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"radio-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n\n <!-- Dropdown -->\n @if (QuestionType === 'dropdown') {\n <select\n [formControl]=\"Control\"\n [disabled]=\"Disabled\"\n class=\"question-dropdown\"\n >\n <option value=\"\">Select an option...</option>\n @for (option of Options; track TrackByValue($index, option)) {\n <option [value]=\"option.value\">\n {{ option.label }}\n </option>\n }\n </select>\n }\n\n <!-- Slider -->\n @if (QuestionType === 'slider') {\n <div class=\"question-slider-container\">\n <input\n type=\"range\"\n class=\"mj-slider question-slider\"\n [min]=\"GetSliderConfig().min\"\n [max]=\"GetSliderConfig().max\"\n [step]=\"GetSliderConfig().step || 1\"\n [value]=\"$any(Value) || GetSliderConfig().min\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange(+$any($event.target).value)\"\n />\n <div class=\"slider-value\">\n {{ Value || GetSliderConfig().min }}{{ GetSliderConfig().suffix || '' }}\n </div>\n </div>\n }\n\n <!-- Date Range -->\n @if (QuestionType === 'daterange') {\n <div class=\"question-daterange\">\n <mj-datepicker\n [ngModel]=\"$any(Value)?.start\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeStartChange($any($event))\"\n Placeholder=\"Start Date\"\n class=\"daterange-start\"\n ></mj-datepicker>\n <span class=\"daterange-separator\">to</span>\n <mj-datepicker\n [ngModel]=\"$any(Value)?.end\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeEndChange($any($event))\"\n Placeholder=\"End Date\"\n class=\"daterange-end\"\n ></mj-datepicker>\n </div>\n }\n\n <!-- Time Picker -->\n @if (QuestionType === 'time') {\n <input\n type=\"time\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Checkboxes -->\n @if (QuestionType === 'checkbox') {\n <div class=\"question-checkbox-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"checkbox-option\">\n <input\n type=\"checkbox\"\n class=\"mj-checkbox\"\n [checked]=\"IsChecked(option)\"\n [disabled]=\"Disabled\"\n (change)=\"ToggleCheckbox(option)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"checkbox-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n </div><!-- end input-container -->\n\n <!-- Validation Error -->\n @if (Control.invalid && Control.touched) {\n <div class=\"validation-error\">\n @if (Control.errors?.['required']) {\n <span>This field is required</span>\n }\n @if (Control.errors?.['email']) {\n <span>Please enter a valid email address</span>\n }\n @if (Control.errors?.['min']) {\n <span>Value must be at least {{ Min }}</span>\n }\n @if (Control.errors?.['max']) {\n <span>Value must be at most {{ Max }}</span>\n }\n </div>\n }\n</div>\n", styles: [".form-question {\n margin-bottom: 16px;\n}\n\n.question-label {\n display: block;\n font-weight: 500;\n margin-bottom: 6px;\n color: var(--mj-text-primary);\n font-size: 14px;\n}\n\n.required-indicator {\n color: var(--mj-status-error);\n margin-left: 2px;\n}\n\n.help-text {\n font-size: 12px;\n color: var(--mj-text-secondary);\n margin-bottom: 8px;\n font-style: italic;\n}\n\n/* Default widths - controlled by input-container width classes */\n.question-input,\n.question-textarea,\n.question-dropdown {\n width: 100%;\n max-width: 100%;\n}\n\n/* A value or placeholder longer than the field reads as truncated rather than\n chopped mid-word \u2014 single-line controls cannot wrap. */\n.question-input,\n.question-dropdown {\n text-overflow: ellipsis;\n}\n\n.question-dropdown {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 14px;\n font-family: inherit;\n background-color: var(--mj-bg-surface);\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 9L1 4h10z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n background-size: 12px;\n padding-right: 32px;\n cursor: pointer;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n color: var(--mj-text-primary);\n}\n\n.question-dropdown:hover {\n border-color: var(--mj-border-strong);\n}\n\n.question-dropdown:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.question-dropdown:disabled {\n background-color: var(--mj-bg-surface-sunken);\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.question-textarea {\n resize: vertical;\n min-height: 100px;\n}\n\n.question-buttongroup {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Wraps rather than scrolls: the previous overflow-x scroller sat inside a vertically\n scrolling chat column, so overlay scrollbars hid the extra options outright. Matches\n .footer-choice-button, the sibling button path in this same form. */\n.buttongroup-option {\n flex: 0 1 auto;\n max-width: 100%;\n white-space: normal;\n text-align: left;\n line-height: 1.3;\n}\n\n.buttongroup-option i {\n margin-right: 6px;\n}\n\n.question-radio-group,\n.question-checkbox-group {\n display: flex;\n flex-wrap: wrap;\n gap: 12px 16px;\n}\n\n.radio-option,\n.checkbox-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n /* Basis is the option's own content width, but it may shrink: short options share a\n row, while an option too wide for the row takes the row to itself and wraps its\n label instead of overflowing (and being clipped by .response-form's overflow:hidden). */\n flex: 0 1 auto;\n min-width: min(120px, 100%);\n max-width: 100%;\n}\n\n.radio-option > input,\n.checkbox-option > input {\n flex: 0 0 auto;\n /* nudge the control onto the first text line now that options are top-aligned */\n margin-top: 3px;\n}\n\n.radio-label,\n.checkbox-label {\n cursor: pointer;\n user-select: none;\n margin: 0;\n display: flex;\n align-items: flex-start;\n gap: 6px;\n /* the only text in the form that had no size of its own \u2014 it inherited the host app's\n body size (~16px) while every sibling control here is 14px */\n font-size: 14px;\n /* without this the label is stuck at its content width and cannot wrap */\n min-width: 0;\n line-height: 1.4;\n overflow-wrap: anywhere;\n}\n\n.radio-label i,\n.checkbox-label i {\n color: var(--mj-text-secondary);\n flex: 0 0 auto;\n margin-top: 2px;\n}\n\n.validation-error {\n color: var(--mj-status-error);\n font-size: 12px;\n margin-top: 4px;\n}\n\n/* Required field styling */\n.form-question.required .question-label {\n font-weight: 600;\n}\n\n/* Slider styling */\n.question-slider-container {\n width: 100%;\n}\n\n.question-slider {\n width: 100%;\n margin-bottom: 8px;\n}\n\n.slider-value {\n text-align: center;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-brand-primary);\n padding: 4px 0;\n}\n\n/* Date range styling */\n.question-daterange {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.daterange-start,\n.daterange-end {\n flex: 1;\n min-width: 150px;\n}\n\n.daterange-separator {\n font-weight: 500;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n}\n\n/* Mobile adjustments for date range */\n@media (max-width: 480px) {\n .question-daterange {\n flex-direction: column;\n align-items: stretch;\n }\n\n .daterange-separator {\n text-align: center;\n padding: 4px 0;\n }\n}\n\n/* ===== Semantic Width Classes ===== */\n/* Controls field width based on expected content length */\n\n.input-container {\n display: block;\n}\n\n/* Narrow fields - for numbers, dates, times, short codes */\n.input-container.width-narrow {\n max-width: 200px;\n}\n\n.input-container.width-narrow .question-input,\n.input-container.width-narrow .question-dropdown {\n width: 100%;\n}\n\n/* Medium fields - for names, cities, phone numbers (DEFAULT) */\n.input-container.width-medium {\n max-width: 560px;\n}\n\n.input-container.width-medium .question-input,\n.input-container.width-medium .question-dropdown {\n width: 100%;\n}\n\n/* Wide fields - for emails, URLs, long text */\n.input-container.width-wide {\n max-width: 100%;\n}\n\n.input-container.width-wide .question-input,\n.input-container.width-wide .question-dropdown {\n width: 100%;\n}\n\n/* Full width - for textarea, search fields */\n.input-container.width-full {\n max-width: 100%;\n}\n\n.input-container.width-full .question-input,\n.input-container.width-full .question-textarea,\n.input-container.width-full .question-dropdown {\n width: 100%;\n}\n\n/* Auto-width - for dropdowns, radio, checkbox (sizes to content) */\n.input-container.width-auto {\n max-width: 100%;\n min-width: 150px;\n width: auto;\n}\n\n/* widthHint: 'auto' is legal on any question type, and this container used to cap\n every control it held at 350px. Keep that cap as the default so lifting it off the\n container changes only the controls this fix targets \u2014 not sliders, date ranges or\n the markdown panel, which also live here. */\n.input-container.width-auto > * {\n max-width: 350px;\n}\n\n/* The select sizes to its widest option rather than to a fixed cap, so a long\n selected label stays readable; the container is what bounds it. */\n.input-container.width-auto > .question-dropdown {\n width: auto;\n max-width: 100%;\n}\n\n/* Choice groups need the full width to lay their options out \u2014 that is the whole\n point of the wrapping rules above. */\n.input-container.width-auto > .question-radio-group,\n.input-container.width-auto > .question-checkbox-group,\n.input-container.width-auto > .question-buttongroup {\n max-width: 100%;\n}\n\n/* Responsive adjustments for smaller screens */\n@media (max-width: 600px) {\n .input-container.width-narrow,\n .input-container.width-medium,\n .input-container.width-wide,\n .input-container.width-auto {\n max-width: 100%;\n }\n\n /* release the width-auto child cap too, or it binds where the container no longer does */\n .input-container.width-auto > * {\n max-width: 100%;\n }\n}\n\n/* \u2500\u2500 Markdown textarea (question.type.markdown) \u2014 rendered preview + Edit toggle \u2500\u2500 */\n.markdown-field {\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n.markdown-field-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 6px 10px;\n background: var(--mj-bg-surface-sunken);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.markdown-field-hint {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-muted);\n}\n.markdown-field-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font: inherit;\n font-size: 11.5px;\n font-weight: 600;\n padding: 4px 10px;\n border-radius: 7px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: color 0.15s ease, border-color 0.15s ease;\n}\n.markdown-field-toggle:hover:not(:disabled) {\n color: var(--mj-brand-primary);\n border-color: color-mix(in srgb, var(--mj-brand-primary) 45%, var(--mj-border-default));\n}\n.markdown-field-toggle:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.markdown-field-preview {\n padding: 12px 14px;\n max-height: 420px;\n overflow-y: auto;\n font-size: 13.5px;\n line-height: 1.6;\n color: var(--mj-text-primary);\n}\n/* the raw editor inside the panel loses its own border (the panel provides it) */\n.markdown-field .question-textarea {\n border: none;\n border-radius: 0;\n width: 100%;\n}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i3.MJButtonDirective, selector: "button[mjButton], a[mjButton]", inputs: ["variant", "size", "toggleable", "selected", "ariaLabel"], outputs: ["selectedChange"] }, { kind: "component", type: i3.MJNumericInputComponent, selector: "mj-numeric-input", inputs: ["Min", "Max", "Step", "Format", "Decimals", "Disabled", "Placeholder"] }, { kind: "component", type: i3.MJDatepickerComponent, selector: "mj-datepicker", inputs: ["Min", "Max", "Format", "Placeholder", "Disabled"], outputs: ["ValueChange"] }, { kind: "component", type: i4.MarkdownComponent, selector: "mj-markdown", inputs: ["data", "enableHighlight", "enableMermaid", "enableCodeCopy", "enableCollapsibleHeadings", "collapsibleHeadingLevel", "collapsibleDefaultExpanded", "autoExpandLevels", "enableAlerts", "enableSmartypants", "enableSvgRenderer", "enableHtml", "enableJavaScript", "enableHeadingIds", "headingIdPrefix", "enableLineNumbers", "containerClass", "mermaidTheme", "sanitize"], outputs: ["rendered", "headingClick", "codeCopied"] }] });
245
245
  }
246
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormFieldComponent, decorators: [{
246
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormFieldComponent, decorators: [{
247
247
  type: Component,
248
248
  args: [{ standalone: false, selector: 'mj-dynamic-form-field', providers: [
249
249
  {
@@ -251,7 +251,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
251
251
  useExisting: forwardRef(() => DynamicFormFieldComponent),
252
252
  multi: true
253
253
  }
254
- ], template: "<div class=\"form-question\" [class.required]=\"Question.required\">\n <!-- Label -->\n @if (Question.label) {\n <label class=\"question-label\">\n {{ Question.label }}\n @if (Question.required) {\n <span class=\"required-indicator\">*</span>\n }\n </label>\n }\n\n <!-- Help Text -->\n @if (Question.helpText) {\n <div class=\"help-text\">\n {{ Question.helpText }}\n </div>\n }\n\n <!-- Input Container with width class -->\n <div class=\"input-container\" [ngClass]=\"WidthClass\">\n\n <!-- Text Input -->\n @if (QuestionType === 'text') {\n <input\n type=\"text\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Email Input -->\n @if (QuestionType === 'email') {\n <input\n type=\"email\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Textarea (markdown mode: rendered preview by default, Edit toggles the raw editor) -->\n @if (QuestionType === 'textarea') {\n @if (IsMarkdown) {\n <div class=\"markdown-field\" [class.editing]=\"MarkdownEditing\">\n <div class=\"markdown-field-toolbar\">\n <span class=\"markdown-field-hint\">\n <i class=\"fa-brands fa-markdown\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Editing Markdown' : 'Formatted preview' }}\n </span>\n <button\n type=\"button\"\n class=\"markdown-field-toggle\"\n [disabled]=\"Disabled\"\n (click)=\"ToggleMarkdownEditing()\"\n [title]=\"MarkdownEditing ? 'Show the formatted preview' : 'Edit the raw Markdown'\"\n >\n <i class=\"fa-solid\" [ngClass]=\"MarkdownEditing ? 'fa-eye' : 'fa-pen'\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Preview' : 'Edit' }}\n </button>\n </div>\n @if (!MarkdownEditing) {\n <div class=\"markdown-field-preview\">\n <mj-markdown [data]=\"MarkdownValue\"></mj-markdown>\n </div>\n }\n @if (MarkdownEditing) {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"10\"\n ></textarea>\n }\n </div>\n } @else {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"5\"\n ></textarea>\n }\n }\n\n <!-- Number Input -->\n @if (QuestionType === 'number') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 1\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Currency Input -->\n @if (QuestionType === 'currency') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 0.01\"\n [Decimals]=\"2\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Date Picker -->\n @if (QuestionType === 'date') {\n <mj-datepicker\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-datepicker>\n }\n\n <!-- DateTime Picker -->\n @if (QuestionType === 'datetime') {\n <input\n type=\"datetime-local\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Button Group -->\n @if (QuestionType === 'buttongroup') {\n <div class=\"mj-buttongroup question-buttongroup\">\n @for (option of Options; track TrackByValue($index, option)) {\n <button\n mjButton\n [toggleable]=\"true\"\n [selected]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (click)=\"OnValueChange(option.value)\"\n class=\"buttongroup-option\"\n >\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </button>\n }\n </div>\n }\n\n <!-- Radio Buttons -->\n @if (QuestionType === 'radio') {\n <div class=\"question-radio-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"radio-option\">\n <input\n type=\"radio\"\n class=\"mj-radio\"\n [name]=\"Question.id\"\n [value]=\"option.value\"\n [checked]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange(option.value)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"radio-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n\n <!-- Dropdown -->\n @if (QuestionType === 'dropdown') {\n <select\n [formControl]=\"Control\"\n [disabled]=\"Disabled\"\n class=\"question-dropdown\"\n >\n <option value=\"\">Select an option...</option>\n @for (option of Options; track TrackByValue($index, option)) {\n <option [value]=\"option.value\">\n {{ option.label }}\n </option>\n }\n </select>\n }\n\n <!-- Slider -->\n @if (QuestionType === 'slider') {\n <div class=\"question-slider-container\">\n <input\n type=\"range\"\n class=\"mj-slider question-slider\"\n [min]=\"GetSliderConfig().min\"\n [max]=\"GetSliderConfig().max\"\n [step]=\"GetSliderConfig().step || 1\"\n [value]=\"$any(Value) || GetSliderConfig().min\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange(+$any($event.target).value)\"\n />\n <div class=\"slider-value\">\n {{ Value || GetSliderConfig().min }}{{ GetSliderConfig().suffix || '' }}\n </div>\n </div>\n }\n\n <!-- Date Range -->\n @if (QuestionType === 'daterange') {\n <div class=\"question-daterange\">\n <mj-datepicker\n [ngModel]=\"$any(Value)?.start\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeStartChange($any($event))\"\n Placeholder=\"Start Date\"\n class=\"daterange-start\"\n ></mj-datepicker>\n <span class=\"daterange-separator\">to</span>\n <mj-datepicker\n [ngModel]=\"$any(Value)?.end\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeEndChange($any($event))\"\n Placeholder=\"End Date\"\n class=\"daterange-end\"\n ></mj-datepicker>\n </div>\n }\n\n <!-- Time Picker -->\n @if (QuestionType === 'time') {\n <input\n type=\"time\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Checkboxes -->\n @if (QuestionType === 'checkbox') {\n <div class=\"question-checkbox-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"checkbox-option\">\n <input\n type=\"checkbox\"\n class=\"mj-checkbox\"\n [checked]=\"IsChecked(option)\"\n [disabled]=\"Disabled\"\n (change)=\"ToggleCheckbox(option)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"checkbox-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n </div><!-- end input-container -->\n\n <!-- Validation Error -->\n @if (Control.invalid && Control.touched) {\n <div class=\"validation-error\">\n @if (Control.errors?.['required']) {\n <span>This field is required</span>\n }\n @if (Control.errors?.['email']) {\n <span>Please enter a valid email address</span>\n }\n @if (Control.errors?.['min']) {\n <span>Value must be at least {{ Min }}</span>\n }\n @if (Control.errors?.['max']) {\n <span>Value must be at most {{ Max }}</span>\n }\n </div>\n }\n</div>\n", styles: [".form-question {\n margin-bottom: 16px;\n}\n\n.question-label {\n display: block;\n font-weight: 500;\n margin-bottom: 6px;\n color: var(--mj-text-primary);\n font-size: 14px;\n}\n\n.required-indicator {\n color: var(--mj-status-error);\n margin-left: 2px;\n}\n\n.help-text {\n font-size: 12px;\n color: var(--mj-text-secondary);\n margin-bottom: 8px;\n font-style: italic;\n}\n\n/* Default widths - controlled by input-container width classes */\n.question-input,\n.question-textarea,\n.question-dropdown {\n width: 100%;\n max-width: 100%;\n}\n\n.question-dropdown {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 14px;\n font-family: inherit;\n background-color: var(--mj-bg-surface);\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 9L1 4h10z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n background-size: 12px;\n padding-right: 32px;\n cursor: pointer;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n color: var(--mj-text-primary);\n}\n\n.question-dropdown:hover {\n border-color: var(--mj-border-strong);\n}\n\n.question-dropdown:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.question-dropdown:disabled {\n background-color: var(--mj-bg-surface-sunken);\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.question-textarea {\n resize: vertical;\n min-height: 100px;\n}\n\n.question-buttongroup {\n display: flex;\n flex-wrap: nowrap;\n gap: 8px;\n overflow-x: auto;\n overflow-y: hidden;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: thin;\n scrollbar-color: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent) transparent;\n}\n\n.question-buttongroup::-webkit-scrollbar {\n height: 6px;\n}\n\n.question-buttongroup::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.question-buttongroup::-webkit-scrollbar-thumb {\n background: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n border-radius: 3px;\n}\n\n.question-buttongroup::-webkit-scrollbar-thumb:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 50%, transparent);\n}\n\n.buttongroup-option {\n flex: 0 0 auto;\n white-space: nowrap;\n}\n\n.buttongroup-option i {\n margin-right: 6px;\n}\n\n.question-radio-group,\n.question-checkbox-group {\n display: flex;\n flex-wrap: wrap;\n gap: 12px 16px;\n}\n\n.radio-option,\n.checkbox-option {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 120px;\n flex: 0 0 auto;\n}\n\n.radio-label,\n.checkbox-label {\n cursor: pointer;\n user-select: none;\n margin: 0;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.radio-label i,\n.checkbox-label i {\n color: var(--mj-text-secondary);\n}\n\n.validation-error {\n color: var(--mj-status-error);\n font-size: 12px;\n margin-top: 4px;\n}\n\n/* Required field styling */\n.form-question.required .question-label {\n font-weight: 600;\n}\n\n/* Slider styling */\n.question-slider-container {\n width: 100%;\n}\n\n.question-slider {\n width: 100%;\n margin-bottom: 8px;\n}\n\n.slider-value {\n text-align: center;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-brand-primary);\n padding: 4px 0;\n}\n\n/* Date range styling */\n.question-daterange {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.daterange-start,\n.daterange-end {\n flex: 1;\n min-width: 150px;\n}\n\n.daterange-separator {\n font-weight: 500;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n}\n\n/* Mobile adjustments for date range */\n@media (max-width: 480px) {\n .question-daterange {\n flex-direction: column;\n align-items: stretch;\n }\n\n .daterange-separator {\n text-align: center;\n padding: 4px 0;\n }\n}\n\n/* ===== Semantic Width Classes ===== */\n/* Controls field width based on expected content length */\n\n.input-container {\n display: block;\n}\n\n/* Narrow fields - for numbers, dates, times, short codes */\n.input-container.width-narrow {\n max-width: 200px;\n}\n\n.input-container.width-narrow .question-input,\n.input-container.width-narrow .question-dropdown {\n width: 100%;\n}\n\n/* Medium fields - for names, cities, phone numbers (DEFAULT) */\n.input-container.width-medium {\n max-width: 450px;\n}\n\n.input-container.width-medium .question-input,\n.input-container.width-medium .question-dropdown {\n width: 100%;\n}\n\n/* Wide fields - for emails, URLs, long text */\n.input-container.width-wide {\n max-width: 100%;\n}\n\n.input-container.width-wide .question-input,\n.input-container.width-wide .question-dropdown {\n width: 100%;\n}\n\n/* Full width - for textarea, search fields */\n.input-container.width-full {\n max-width: 100%;\n}\n\n.input-container.width-full .question-input,\n.input-container.width-full .question-textarea,\n.input-container.width-full .question-dropdown {\n width: 100%;\n}\n\n/* Auto-width - for dropdowns, radio, checkbox (sizes to content) */\n.input-container.width-auto {\n max-width: 350px;\n min-width: 150px;\n width: auto;\n}\n\n.input-container.width-auto .question-dropdown {\n width: 100%;\n}\n\n/* Responsive adjustments for smaller screens */\n@media (max-width: 600px) {\n .input-container.width-narrow,\n .input-container.width-medium,\n .input-container.width-wide,\n .input-container.width-auto {\n max-width: 100%;\n }\n}\n\n/* \u2500\u2500 Markdown textarea (question.type.markdown) \u2014 rendered preview + Edit toggle \u2500\u2500 */\n.markdown-field {\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n.markdown-field-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 6px 10px;\n background: var(--mj-bg-surface-sunken);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.markdown-field-hint {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-muted);\n}\n.markdown-field-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font: inherit;\n font-size: 11.5px;\n font-weight: 600;\n padding: 4px 10px;\n border-radius: 7px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: color 0.15s ease, border-color 0.15s ease;\n}\n.markdown-field-toggle:hover:not(:disabled) {\n color: var(--mj-brand-primary);\n border-color: color-mix(in srgb, var(--mj-brand-primary) 45%, var(--mj-border-default));\n}\n.markdown-field-toggle:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.markdown-field-preview {\n padding: 12px 14px;\n max-height: 420px;\n overflow-y: auto;\n font-size: 13.5px;\n line-height: 1.6;\n color: var(--mj-text-primary);\n}\n/* the raw editor inside the panel loses its own border (the panel provides it) */\n.markdown-field .question-textarea {\n border: none;\n border-radius: 0;\n width: 100%;\n}\n"] }]
254
+ ], template: "<div class=\"form-question\" [class.required]=\"Question.required\">\n <!-- Label -->\n @if (Question.label) {\n <label class=\"question-label\">\n {{ Question.label }}\n @if (Question.required) {\n <span class=\"required-indicator\">*</span>\n }\n </label>\n }\n\n <!-- Help Text -->\n @if (Question.helpText) {\n <div class=\"help-text\">\n {{ Question.helpText }}\n </div>\n }\n\n <!-- Input Container with width class -->\n <div class=\"input-container\" [ngClass]=\"WidthClass\">\n\n <!-- Text Input -->\n @if (QuestionType === 'text') {\n <input\n type=\"text\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Email Input -->\n @if (QuestionType === 'email') {\n <input\n type=\"email\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Textarea (markdown mode: rendered preview by default, Edit toggles the raw editor) -->\n @if (QuestionType === 'textarea') {\n @if (IsMarkdown) {\n <div class=\"markdown-field\" [class.editing]=\"MarkdownEditing\">\n <div class=\"markdown-field-toolbar\">\n <span class=\"markdown-field-hint\">\n <i class=\"fa-brands fa-markdown\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Editing Markdown' : 'Formatted preview' }}\n </span>\n <button\n type=\"button\"\n class=\"markdown-field-toggle\"\n [disabled]=\"Disabled\"\n (click)=\"ToggleMarkdownEditing()\"\n [title]=\"MarkdownEditing ? 'Show the formatted preview' : 'Edit the raw Markdown'\"\n >\n <i class=\"fa-solid\" [ngClass]=\"MarkdownEditing ? 'fa-eye' : 'fa-pen'\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Preview' : 'Edit' }}\n </button>\n </div>\n @if (!MarkdownEditing) {\n <div class=\"markdown-field-preview\">\n <mj-markdown [data]=\"MarkdownValue\"></mj-markdown>\n </div>\n }\n @if (MarkdownEditing) {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"10\"\n ></textarea>\n }\n </div>\n } @else {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"5\"\n ></textarea>\n }\n }\n\n <!-- Number Input -->\n @if (QuestionType === 'number') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 1\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Currency Input -->\n @if (QuestionType === 'currency') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 0.01\"\n [Decimals]=\"2\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Date Picker -->\n @if (QuestionType === 'date') {\n <mj-datepicker\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-datepicker>\n }\n\n <!-- DateTime Picker -->\n @if (QuestionType === 'datetime') {\n <input\n type=\"datetime-local\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Button Group -->\n @if (QuestionType === 'buttongroup') {\n <div class=\"mj-buttongroup question-buttongroup\">\n @for (option of Options; track TrackByValue($index, option)) {\n <button\n mjButton\n [toggleable]=\"true\"\n [selected]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (click)=\"OnValueChange(option.value)\"\n class=\"buttongroup-option\"\n >\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </button>\n }\n </div>\n }\n\n <!-- Radio Buttons -->\n @if (QuestionType === 'radio') {\n <div class=\"question-radio-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"radio-option\">\n <input\n type=\"radio\"\n class=\"mj-radio\"\n [name]=\"Question.id\"\n [value]=\"option.value\"\n [checked]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange(option.value)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"radio-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n\n <!-- Dropdown -->\n @if (QuestionType === 'dropdown') {\n <select\n [formControl]=\"Control\"\n [disabled]=\"Disabled\"\n class=\"question-dropdown\"\n >\n <option value=\"\">Select an option...</option>\n @for (option of Options; track TrackByValue($index, option)) {\n <option [value]=\"option.value\">\n {{ option.label }}\n </option>\n }\n </select>\n }\n\n <!-- Slider -->\n @if (QuestionType === 'slider') {\n <div class=\"question-slider-container\">\n <input\n type=\"range\"\n class=\"mj-slider question-slider\"\n [min]=\"GetSliderConfig().min\"\n [max]=\"GetSliderConfig().max\"\n [step]=\"GetSliderConfig().step || 1\"\n [value]=\"$any(Value) || GetSliderConfig().min\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange(+$any($event.target).value)\"\n />\n <div class=\"slider-value\">\n {{ Value || GetSliderConfig().min }}{{ GetSliderConfig().suffix || '' }}\n </div>\n </div>\n }\n\n <!-- Date Range -->\n @if (QuestionType === 'daterange') {\n <div class=\"question-daterange\">\n <mj-datepicker\n [ngModel]=\"$any(Value)?.start\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeStartChange($any($event))\"\n Placeholder=\"Start Date\"\n class=\"daterange-start\"\n ></mj-datepicker>\n <span class=\"daterange-separator\">to</span>\n <mj-datepicker\n [ngModel]=\"$any(Value)?.end\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeEndChange($any($event))\"\n Placeholder=\"End Date\"\n class=\"daterange-end\"\n ></mj-datepicker>\n </div>\n }\n\n <!-- Time Picker -->\n @if (QuestionType === 'time') {\n <input\n type=\"time\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Checkboxes -->\n @if (QuestionType === 'checkbox') {\n <div class=\"question-checkbox-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"checkbox-option\">\n <input\n type=\"checkbox\"\n class=\"mj-checkbox\"\n [checked]=\"IsChecked(option)\"\n [disabled]=\"Disabled\"\n (change)=\"ToggleCheckbox(option)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"checkbox-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n </div><!-- end input-container -->\n\n <!-- Validation Error -->\n @if (Control.invalid && Control.touched) {\n <div class=\"validation-error\">\n @if (Control.errors?.['required']) {\n <span>This field is required</span>\n }\n @if (Control.errors?.['email']) {\n <span>Please enter a valid email address</span>\n }\n @if (Control.errors?.['min']) {\n <span>Value must be at least {{ Min }}</span>\n }\n @if (Control.errors?.['max']) {\n <span>Value must be at most {{ Max }}</span>\n }\n </div>\n }\n</div>\n", styles: [".form-question {\n margin-bottom: 16px;\n}\n\n.question-label {\n display: block;\n font-weight: 500;\n margin-bottom: 6px;\n color: var(--mj-text-primary);\n font-size: 14px;\n}\n\n.required-indicator {\n color: var(--mj-status-error);\n margin-left: 2px;\n}\n\n.help-text {\n font-size: 12px;\n color: var(--mj-text-secondary);\n margin-bottom: 8px;\n font-style: italic;\n}\n\n/* Default widths - controlled by input-container width classes */\n.question-input,\n.question-textarea,\n.question-dropdown {\n width: 100%;\n max-width: 100%;\n}\n\n/* A value or placeholder longer than the field reads as truncated rather than\n chopped mid-word \u2014 single-line controls cannot wrap. */\n.question-input,\n.question-dropdown {\n text-overflow: ellipsis;\n}\n\n.question-dropdown {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 4px;\n font-size: 14px;\n font-family: inherit;\n background-color: var(--mj-bg-surface);\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 9L1 4h10z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n background-size: 12px;\n padding-right: 32px;\n cursor: pointer;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n color: var(--mj-text-primary);\n}\n\n.question-dropdown:hover {\n border-color: var(--mj-border-strong);\n}\n\n.question-dropdown:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.question-dropdown:disabled {\n background-color: var(--mj-bg-surface-sunken);\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.question-textarea {\n resize: vertical;\n min-height: 100px;\n}\n\n.question-buttongroup {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Wraps rather than scrolls: the previous overflow-x scroller sat inside a vertically\n scrolling chat column, so overlay scrollbars hid the extra options outright. Matches\n .footer-choice-button, the sibling button path in this same form. */\n.buttongroup-option {\n flex: 0 1 auto;\n max-width: 100%;\n white-space: normal;\n text-align: left;\n line-height: 1.3;\n}\n\n.buttongroup-option i {\n margin-right: 6px;\n}\n\n.question-radio-group,\n.question-checkbox-group {\n display: flex;\n flex-wrap: wrap;\n gap: 12px 16px;\n}\n\n.radio-option,\n.checkbox-option {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n /* Basis is the option's own content width, but it may shrink: short options share a\n row, while an option too wide for the row takes the row to itself and wraps its\n label instead of overflowing (and being clipped by .response-form's overflow:hidden). */\n flex: 0 1 auto;\n min-width: min(120px, 100%);\n max-width: 100%;\n}\n\n.radio-option > input,\n.checkbox-option > input {\n flex: 0 0 auto;\n /* nudge the control onto the first text line now that options are top-aligned */\n margin-top: 3px;\n}\n\n.radio-label,\n.checkbox-label {\n cursor: pointer;\n user-select: none;\n margin: 0;\n display: flex;\n align-items: flex-start;\n gap: 6px;\n /* the only text in the form that had no size of its own \u2014 it inherited the host app's\n body size (~16px) while every sibling control here is 14px */\n font-size: 14px;\n /* without this the label is stuck at its content width and cannot wrap */\n min-width: 0;\n line-height: 1.4;\n overflow-wrap: anywhere;\n}\n\n.radio-label i,\n.checkbox-label i {\n color: var(--mj-text-secondary);\n flex: 0 0 auto;\n margin-top: 2px;\n}\n\n.validation-error {\n color: var(--mj-status-error);\n font-size: 12px;\n margin-top: 4px;\n}\n\n/* Required field styling */\n.form-question.required .question-label {\n font-weight: 600;\n}\n\n/* Slider styling */\n.question-slider-container {\n width: 100%;\n}\n\n.question-slider {\n width: 100%;\n margin-bottom: 8px;\n}\n\n.slider-value {\n text-align: center;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-brand-primary);\n padding: 4px 0;\n}\n\n/* Date range styling */\n.question-daterange {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.daterange-start,\n.daterange-end {\n flex: 1;\n min-width: 150px;\n}\n\n.daterange-separator {\n font-weight: 500;\n color: var(--mj-text-secondary);\n white-space: nowrap;\n}\n\n/* Mobile adjustments for date range */\n@media (max-width: 480px) {\n .question-daterange {\n flex-direction: column;\n align-items: stretch;\n }\n\n .daterange-separator {\n text-align: center;\n padding: 4px 0;\n }\n}\n\n/* ===== Semantic Width Classes ===== */\n/* Controls field width based on expected content length */\n\n.input-container {\n display: block;\n}\n\n/* Narrow fields - for numbers, dates, times, short codes */\n.input-container.width-narrow {\n max-width: 200px;\n}\n\n.input-container.width-narrow .question-input,\n.input-container.width-narrow .question-dropdown {\n width: 100%;\n}\n\n/* Medium fields - for names, cities, phone numbers (DEFAULT) */\n.input-container.width-medium {\n max-width: 560px;\n}\n\n.input-container.width-medium .question-input,\n.input-container.width-medium .question-dropdown {\n width: 100%;\n}\n\n/* Wide fields - for emails, URLs, long text */\n.input-container.width-wide {\n max-width: 100%;\n}\n\n.input-container.width-wide .question-input,\n.input-container.width-wide .question-dropdown {\n width: 100%;\n}\n\n/* Full width - for textarea, search fields */\n.input-container.width-full {\n max-width: 100%;\n}\n\n.input-container.width-full .question-input,\n.input-container.width-full .question-textarea,\n.input-container.width-full .question-dropdown {\n width: 100%;\n}\n\n/* Auto-width - for dropdowns, radio, checkbox (sizes to content) */\n.input-container.width-auto {\n max-width: 100%;\n min-width: 150px;\n width: auto;\n}\n\n/* widthHint: 'auto' is legal on any question type, and this container used to cap\n every control it held at 350px. Keep that cap as the default so lifting it off the\n container changes only the controls this fix targets \u2014 not sliders, date ranges or\n the markdown panel, which also live here. */\n.input-container.width-auto > * {\n max-width: 350px;\n}\n\n/* The select sizes to its widest option rather than to a fixed cap, so a long\n selected label stays readable; the container is what bounds it. */\n.input-container.width-auto > .question-dropdown {\n width: auto;\n max-width: 100%;\n}\n\n/* Choice groups need the full width to lay their options out \u2014 that is the whole\n point of the wrapping rules above. */\n.input-container.width-auto > .question-radio-group,\n.input-container.width-auto > .question-checkbox-group,\n.input-container.width-auto > .question-buttongroup {\n max-width: 100%;\n}\n\n/* Responsive adjustments for smaller screens */\n@media (max-width: 600px) {\n .input-container.width-narrow,\n .input-container.width-medium,\n .input-container.width-wide,\n .input-container.width-auto {\n max-width: 100%;\n }\n\n /* release the width-auto child cap too, or it binds where the container no longer does */\n .input-container.width-auto > * {\n max-width: 100%;\n }\n}\n\n/* \u2500\u2500 Markdown textarea (question.type.markdown) \u2014 rendered preview + Edit toggle \u2500\u2500 */\n.markdown-field {\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n.markdown-field-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 6px 10px;\n background: var(--mj-bg-surface-sunken);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.markdown-field-hint {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-muted);\n}\n.markdown-field-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font: inherit;\n font-size: 11.5px;\n font-weight: 600;\n padding: 4px 10px;\n border-radius: 7px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: color 0.15s ease, border-color 0.15s ease;\n}\n.markdown-field-toggle:hover:not(:disabled) {\n color: var(--mj-brand-primary);\n border-color: color-mix(in srgb, var(--mj-brand-primary) 45%, var(--mj-border-default));\n}\n.markdown-field-toggle:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.markdown-field-preview {\n padding: 12px 14px;\n max-height: 420px;\n overflow-y: auto;\n font-size: 13.5px;\n line-height: 1.6;\n color: var(--mj-text-primary);\n}\n/* the raw editor inside the panel loses its own border (the panel provides it) */\n.markdown-field .question-textarea {\n border: none;\n border-radius: 0;\n width: 100%;\n}\n"] }]
255
255
  }], propDecorators: { Question: [{
256
256
  type: Input
257
257
  }], Control: [{
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-form-field.component.js","sourceRoot":"","sources":["../../../src/lib/dynamic-form-field/dynamic-form-field.component.ts","../../../src/lib/dynamic-form-field/dynamic-form-field.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Dynamic Form Field Component\n *\n * Renders a single form field based on a `FormQuestion` definition from\n * `@memberjunction/ai-core-plus`. Supports 14+ input types including text,\n * textarea, email, number, currency, date, datetime, time, daterange,\n * buttongroup, radio, dropdown, checkbox, and slider.\n *\n * Implements `ControlValueAccessor` for seamless integration with Angular\n * Reactive Forms. Width is automatically sized based on field type or an\n * explicit `widthHint` on the question definition.\n *\n * @example\n * ```html\n * <mj-dynamic-form-field\n * [Question]=\"question\"\n * [Control]=\"formGroup.get(question.id)\"\n * [formControlName]=\"question.id\">\n * </mj-dynamic-form-field>\n * ```\n *\n * @module @memberjunction/ng-forms\n */\n\nimport { Component, Input, forwardRef, OnInit } from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl } from '@angular/forms';\nimport { FormQuestion, FormOption } from '@memberjunction/ai-core-plus';\n\n@Component({\n standalone: false,\n selector: 'mj-dynamic-form-field',\n templateUrl: './dynamic-form-field.component.html',\n styleUrls: ['./dynamic-form-field.component.css'],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => DynamicFormFieldComponent),\n multi: true\n }\n ]\n})\nexport class DynamicFormFieldComponent implements ControlValueAccessor, OnInit {\n /**\n * The question definition that drives this field's rendering.\n * Determines the input type, label, validation, options, and layout.\n */\n @Input() Question!: FormQuestion;\n\n /**\n * The reactive FormControl bound to this field.\n * Used for validation state display and dropdown binding.\n */\n @Input() Control!: FormControl;\n\n /** Current field value (managed by ControlValueAccessor) */\n public Value: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null = null;\n\n /** Whether this field is disabled */\n public Disabled = false;\n\n private onChange: (value: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null) => void = () => {};\n private onTouched: () => void = () => {};\n\n ngOnInit(): void {\n // No-op; field is configured via inputs\n }\n\n /**\n * Resolved question type string (handles both simple string and object types).\n */\n public get QuestionType(): string {\n return typeof this.Question.type === 'string'\n ? this.Question.type\n : this.Question.type.type;\n }\n\n /**\n * Whether this is a choice-based question (buttongroup, radio, dropdown, checkbox).\n */\n public get IsChoiceQuestion(): boolean {\n return ['buttongroup', 'radio', 'dropdown', 'checkbox'].includes(this.QuestionType);\n }\n\n /**\n * Textarea only: whether the value renders as formatted Markdown with an Edit toggle\n * (question.type.markdown === true). Used for rich agent-authored content the human\n * reviews more than types — e.g. the Plan Mode approval form.\n */\n public get IsMarkdown(): boolean {\n return (\n this.QuestionType === 'textarea' &&\n typeof this.Question.type === 'object' &&\n 'markdown' in this.Question.type &&\n this.Question.type.markdown === true\n );\n }\n\n /** Markdown textareas start in preview; the user toggles into the raw editor as needed. */\n public MarkdownEditing = false;\n\n /** Flip a markdown textarea between rendered preview and the raw editor. */\n public ToggleMarkdownEditing(): void {\n this.MarkdownEditing = !this.MarkdownEditing;\n }\n\n /** The current value as a string for the markdown preview (empty-safe). */\n public get MarkdownValue(): string {\n return typeof this.Value === 'string' ? this.Value : '';\n }\n\n /**\n * Available options for choice-type questions.\n */\n public get Options(): FormOption[] {\n if (typeof this.Question.type === 'object' && 'options' in this.Question.type) {\n return this.Question.type.options;\n }\n return [];\n }\n\n /**\n * Whether multiple selections are allowed (for checkbox type).\n */\n public get AllowMultiple(): boolean {\n return this.QuestionType === 'checkbox' ||\n (typeof this.Question.type === 'object' && 'multiple' in this.Question.type && !!this.Question.type.multiple);\n }\n\n /**\n * CSS class controlling the field width based on `widthHint` or intelligent defaults.\n *\n * Width mapping:\n * - `'narrow'` (~200px): numbers, dates, times\n * - `'medium'` (~450px): text inputs (default)\n * - `'wide'` (100%): emails, buttongroup, radio, checkbox\n * - `'full'` (100%): textarea\n * - `'auto'` (150–350px): dropdowns\n */\n public get WidthClass(): string {\n if (this.Question.widthHint) {\n return `width-${this.Question.widthHint}`;\n }\n const type = this.QuestionType;\n if (['number', 'currency', 'date', 'datetime', 'time'].includes(type)) return 'width-narrow';\n if (['buttongroup', 'radio', 'checkbox'].includes(type)) return 'width-wide';\n if (type === 'dropdown') return 'width-auto';\n if (type === 'textarea') return 'width-full';\n if (type === 'email') return 'width-wide';\n return 'width-medium';\n }\n\n /** Placeholder text for text-based inputs */\n public get Placeholder(): string | undefined {\n if (typeof this.Question.type === 'object' && 'placeholder' in this.Question.type) {\n return this.Question.type.placeholder as string;\n }\n return undefined;\n }\n\n /** Minimum value for numeric inputs */\n public get Min(): number | undefined {\n if (typeof this.Question.type === 'object' && 'min' in this.Question.type) {\n return this.Question.type.min as number;\n }\n return undefined;\n }\n\n /** Maximum value for numeric inputs */\n public get Max(): number | undefined {\n if (typeof this.Question.type === 'object' && 'max' in this.Question.type) {\n return this.Question.type.max as number;\n }\n return undefined;\n }\n\n /** Step increment for numeric inputs */\n public get Step(): number | undefined {\n if (typeof this.Question.type === 'object' && 'step' in this.Question.type) {\n return this.Question.type.step as number;\n }\n return undefined;\n }\n\n /** Prefix string for currency inputs (e.g. \"$\") */\n public get Prefix(): string | undefined {\n if (typeof this.Question.type === 'object' && 'prefix' in this.Question.type) {\n return this.Question.type.prefix as string;\n }\n return undefined;\n }\n\n /** Suffix string for currency/slider inputs (e.g. \"USD\", \"%\") */\n public get Suffix(): string | undefined {\n if (typeof this.Question.type === 'object' && 'suffix' in this.Question.type) {\n return this.Question.type.suffix as string;\n }\n return undefined;\n }\n\n /** Handle value changes from any input type */\n public OnValueChange(newValue: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null): void {\n this.Value = newValue;\n this.onChange(newValue);\n this.onTouched();\n }\n\n /** Toggle a checkbox option (supports single and multiple selection) */\n public ToggleCheckbox(option: FormOption): void {\n if (!this.AllowMultiple) {\n this.OnValueChange(option.value);\n } else {\n const currentValues = Array.isArray(this.Value) ? this.Value : [];\n const index = currentValues.indexOf(option.value);\n const newValues = index > -1\n ? currentValues.filter((v: string | number | boolean) => v !== option.value)\n : [...currentValues, option.value];\n this.OnValueChange(newValues);\n }\n }\n\n /** Check whether a checkbox option is currently selected */\n public IsChecked(option: FormOption): boolean {\n if (!this.AllowMultiple) {\n return this.Value === option.value;\n }\n return Array.isArray(this.Value) && this.Value.includes(option.value);\n }\n\n /** Get slider configuration with defaults */\n public GetSliderConfig(): { min: number; max: number; step?: number; suffix?: string } {\n if (typeof this.Question.type === 'object' && this.Question.type.type === 'slider') {\n return {\n min: this.Question.type.min,\n max: this.Question.type.max,\n step: this.Question.type.step,\n suffix: this.Question.type.suffix\n };\n }\n return { min: 0, max: 100, step: 1 };\n }\n\n /** Handle date range start date change */\n public OnDateRangeStartChange(value: Date): void {\n const current = (this.Value as Record<string, Date>) || {};\n this.OnValueChange({ ...current, start: value });\n }\n\n /** Handle date range end date change */\n public OnDateRangeEndChange(value: Date): void {\n const current = (this.Value as Record<string, Date>) || {};\n this.OnValueChange({ ...current, end: value });\n }\n\n /** Track-by function for options lists */\n public TrackByValue(_index: number, option: FormOption): string | number | boolean {\n return option.value;\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null): void {\n this.Value = value;\n }\n\n registerOnChange(fn: (value: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.Disabled = isDisabled;\n }\n}\n","<div class=\"form-question\" [class.required]=\"Question.required\">\n <!-- Label -->\n @if (Question.label) {\n <label class=\"question-label\">\n {{ Question.label }}\n @if (Question.required) {\n <span class=\"required-indicator\">*</span>\n }\n </label>\n }\n\n <!-- Help Text -->\n @if (Question.helpText) {\n <div class=\"help-text\">\n {{ Question.helpText }}\n </div>\n }\n\n <!-- Input Container with width class -->\n <div class=\"input-container\" [ngClass]=\"WidthClass\">\n\n <!-- Text Input -->\n @if (QuestionType === 'text') {\n <input\n type=\"text\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Email Input -->\n @if (QuestionType === 'email') {\n <input\n type=\"email\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Textarea (markdown mode: rendered preview by default, Edit toggles the raw editor) -->\n @if (QuestionType === 'textarea') {\n @if (IsMarkdown) {\n <div class=\"markdown-field\" [class.editing]=\"MarkdownEditing\">\n <div class=\"markdown-field-toolbar\">\n <span class=\"markdown-field-hint\">\n <i class=\"fa-brands fa-markdown\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Editing Markdown' : 'Formatted preview' }}\n </span>\n <button\n type=\"button\"\n class=\"markdown-field-toggle\"\n [disabled]=\"Disabled\"\n (click)=\"ToggleMarkdownEditing()\"\n [title]=\"MarkdownEditing ? 'Show the formatted preview' : 'Edit the raw Markdown'\"\n >\n <i class=\"fa-solid\" [ngClass]=\"MarkdownEditing ? 'fa-eye' : 'fa-pen'\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Preview' : 'Edit' }}\n </button>\n </div>\n @if (!MarkdownEditing) {\n <div class=\"markdown-field-preview\">\n <mj-markdown [data]=\"MarkdownValue\"></mj-markdown>\n </div>\n }\n @if (MarkdownEditing) {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"10\"\n ></textarea>\n }\n </div>\n } @else {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"5\"\n ></textarea>\n }\n }\n\n <!-- Number Input -->\n @if (QuestionType === 'number') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 1\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Currency Input -->\n @if (QuestionType === 'currency') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 0.01\"\n [Decimals]=\"2\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Date Picker -->\n @if (QuestionType === 'date') {\n <mj-datepicker\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-datepicker>\n }\n\n <!-- DateTime Picker -->\n @if (QuestionType === 'datetime') {\n <input\n type=\"datetime-local\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Button Group -->\n @if (QuestionType === 'buttongroup') {\n <div class=\"mj-buttongroup question-buttongroup\">\n @for (option of Options; track TrackByValue($index, option)) {\n <button\n mjButton\n [toggleable]=\"true\"\n [selected]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (click)=\"OnValueChange(option.value)\"\n class=\"buttongroup-option\"\n >\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </button>\n }\n </div>\n }\n\n <!-- Radio Buttons -->\n @if (QuestionType === 'radio') {\n <div class=\"question-radio-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"radio-option\">\n <input\n type=\"radio\"\n class=\"mj-radio\"\n [name]=\"Question.id\"\n [value]=\"option.value\"\n [checked]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange(option.value)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"radio-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n\n <!-- Dropdown -->\n @if (QuestionType === 'dropdown') {\n <select\n [formControl]=\"Control\"\n [disabled]=\"Disabled\"\n class=\"question-dropdown\"\n >\n <option value=\"\">Select an option...</option>\n @for (option of Options; track TrackByValue($index, option)) {\n <option [value]=\"option.value\">\n {{ option.label }}\n </option>\n }\n </select>\n }\n\n <!-- Slider -->\n @if (QuestionType === 'slider') {\n <div class=\"question-slider-container\">\n <input\n type=\"range\"\n class=\"mj-slider question-slider\"\n [min]=\"GetSliderConfig().min\"\n [max]=\"GetSliderConfig().max\"\n [step]=\"GetSliderConfig().step || 1\"\n [value]=\"$any(Value) || GetSliderConfig().min\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange(+$any($event.target).value)\"\n />\n <div class=\"slider-value\">\n {{ Value || GetSliderConfig().min }}{{ GetSliderConfig().suffix || '' }}\n </div>\n </div>\n }\n\n <!-- Date Range -->\n @if (QuestionType === 'daterange') {\n <div class=\"question-daterange\">\n <mj-datepicker\n [ngModel]=\"$any(Value)?.start\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeStartChange($any($event))\"\n Placeholder=\"Start Date\"\n class=\"daterange-start\"\n ></mj-datepicker>\n <span class=\"daterange-separator\">to</span>\n <mj-datepicker\n [ngModel]=\"$any(Value)?.end\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeEndChange($any($event))\"\n Placeholder=\"End Date\"\n class=\"daterange-end\"\n ></mj-datepicker>\n </div>\n }\n\n <!-- Time Picker -->\n @if (QuestionType === 'time') {\n <input\n type=\"time\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Checkboxes -->\n @if (QuestionType === 'checkbox') {\n <div class=\"question-checkbox-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"checkbox-option\">\n <input\n type=\"checkbox\"\n class=\"mj-checkbox\"\n [checked]=\"IsChecked(option)\"\n [disabled]=\"Disabled\"\n (change)=\"ToggleCheckbox(option)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"checkbox-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n </div><!-- end input-container -->\n\n <!-- Validation Error -->\n @if (Control.invalid && Control.touched) {\n <div class=\"validation-error\">\n @if (Control.errors?.['required']) {\n <span>This field is required</span>\n }\n @if (Control.errors?.['email']) {\n <span>Please enter a valid email address</span>\n }\n @if (Control.errors?.['min']) {\n <span>Value must be at least {{ Min }}</span>\n }\n @if (Control.errors?.['max']) {\n <span>Value must be at most {{ Max }}</span>\n }\n </div>\n }\n</div>\n"]}
1
+ {"version":3,"file":"dynamic-form-field.component.js","sourceRoot":"","sources":["../../../src/lib/dynamic-form-field/dynamic-form-field.component.ts","../../../src/lib/dynamic-form-field/dynamic-form-field.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Dynamic Form Field Component\n *\n * Renders a single form field based on a `FormQuestion` definition from\n * `@memberjunction/ai-core-plus`. Supports 14+ input types including text,\n * textarea, email, number, currency, date, datetime, time, daterange,\n * buttongroup, radio, dropdown, checkbox, and slider.\n *\n * Implements `ControlValueAccessor` for seamless integration with Angular\n * Reactive Forms. Width is automatically sized based on field type or an\n * explicit `widthHint` on the question definition.\n *\n * @example\n * ```html\n * <mj-dynamic-form-field\n * [Question]=\"question\"\n * [Control]=\"formGroup.get(question.id)\"\n * [formControlName]=\"question.id\">\n * </mj-dynamic-form-field>\n * ```\n *\n * @module @memberjunction/ng-forms\n */\n\nimport { Component, Input, forwardRef, OnInit } from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl } from '@angular/forms';\nimport { FormQuestion, FormOption } from '@memberjunction/ai-core-plus';\n\n@Component({\n standalone: false,\n selector: 'mj-dynamic-form-field',\n templateUrl: './dynamic-form-field.component.html',\n styleUrls: ['./dynamic-form-field.component.css'],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => DynamicFormFieldComponent),\n multi: true\n }\n ]\n})\nexport class DynamicFormFieldComponent implements ControlValueAccessor, OnInit {\n /**\n * The question definition that drives this field's rendering.\n * Determines the input type, label, validation, options, and layout.\n */\n @Input() Question!: FormQuestion;\n\n /**\n * The reactive FormControl bound to this field.\n * Used for validation state display and dropdown binding.\n */\n @Input() Control!: FormControl;\n\n /** Current field value (managed by ControlValueAccessor) */\n public Value: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null = null;\n\n /** Whether this field is disabled */\n public Disabled = false;\n\n private onChange: (value: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null) => void = () => {};\n private onTouched: () => void = () => {};\n\n ngOnInit(): void {\n // No-op; field is configured via inputs\n }\n\n /**\n * Resolved question type string (handles both simple string and object types).\n */\n public get QuestionType(): string {\n return typeof this.Question.type === 'string'\n ? this.Question.type\n : this.Question.type.type;\n }\n\n /**\n * Whether this is a choice-based question (buttongroup, radio, dropdown, checkbox).\n */\n public get IsChoiceQuestion(): boolean {\n return ['buttongroup', 'radio', 'dropdown', 'checkbox'].includes(this.QuestionType);\n }\n\n /**\n * Textarea only: whether the value renders as formatted Markdown with an Edit toggle\n * (question.type.markdown === true). Used for rich agent-authored content the human\n * reviews more than types — e.g. the Plan Mode approval form.\n */\n public get IsMarkdown(): boolean {\n return (\n this.QuestionType === 'textarea' &&\n typeof this.Question.type === 'object' &&\n 'markdown' in this.Question.type &&\n this.Question.type.markdown === true\n );\n }\n\n /** Markdown textareas start in preview; the user toggles into the raw editor as needed. */\n public MarkdownEditing = false;\n\n /** Flip a markdown textarea between rendered preview and the raw editor. */\n public ToggleMarkdownEditing(): void {\n this.MarkdownEditing = !this.MarkdownEditing;\n }\n\n /** The current value as a string for the markdown preview (empty-safe). */\n public get MarkdownValue(): string {\n return typeof this.Value === 'string' ? this.Value : '';\n }\n\n /**\n * Available options for choice-type questions.\n */\n public get Options(): FormOption[] {\n if (typeof this.Question.type === 'object' && 'options' in this.Question.type) {\n return this.Question.type.options;\n }\n return [];\n }\n\n /**\n * Whether multiple selections are allowed (for checkbox type).\n */\n public get AllowMultiple(): boolean {\n return this.QuestionType === 'checkbox' ||\n (typeof this.Question.type === 'object' && 'multiple' in this.Question.type && !!this.Question.type.multiple);\n }\n\n /**\n * CSS class controlling the field width based on `widthHint` or intelligent defaults.\n *\n * Width mapping:\n * - `'narrow'` (~200px): numbers, dates, times\n * - `'medium'` (~450px): text inputs (default)\n * - `'wide'` (100%): emails, buttongroup, radio, checkbox\n * - `'full'` (100%): textarea\n * - `'auto'` (150–350px): dropdowns\n */\n public get WidthClass(): string {\n if (this.Question.widthHint) {\n return `width-${this.Question.widthHint}`;\n }\n const type = this.QuestionType;\n if (['number', 'currency', 'date', 'datetime', 'time'].includes(type)) return 'width-narrow';\n if (['buttongroup', 'radio', 'checkbox'].includes(type)) return 'width-wide';\n if (type === 'dropdown') return 'width-auto';\n if (type === 'textarea') return 'width-full';\n if (type === 'email') return 'width-wide';\n return 'width-medium';\n }\n\n /** Placeholder text for text-based inputs */\n public get Placeholder(): string | undefined {\n if (typeof this.Question.type === 'object' && 'placeholder' in this.Question.type) {\n return this.Question.type.placeholder as string;\n }\n return undefined;\n }\n\n /** Minimum value for numeric inputs */\n public get Min(): number | undefined {\n if (typeof this.Question.type === 'object' && 'min' in this.Question.type) {\n return this.Question.type.min as number;\n }\n return undefined;\n }\n\n /** Maximum value for numeric inputs */\n public get Max(): number | undefined {\n if (typeof this.Question.type === 'object' && 'max' in this.Question.type) {\n return this.Question.type.max as number;\n }\n return undefined;\n }\n\n /** Step increment for numeric inputs */\n public get Step(): number | undefined {\n if (typeof this.Question.type === 'object' && 'step' in this.Question.type) {\n return this.Question.type.step as number;\n }\n return undefined;\n }\n\n /** Prefix string for currency inputs (e.g. \"$\") */\n public get Prefix(): string | undefined {\n if (typeof this.Question.type === 'object' && 'prefix' in this.Question.type) {\n return this.Question.type.prefix as string;\n }\n return undefined;\n }\n\n /** Suffix string for currency/slider inputs (e.g. \"USD\", \"%\") */\n public get Suffix(): string | undefined {\n if (typeof this.Question.type === 'object' && 'suffix' in this.Question.type) {\n return this.Question.type.suffix as string;\n }\n return undefined;\n }\n\n /** Handle value changes from any input type */\n public OnValueChange(newValue: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null): void {\n this.Value = newValue;\n this.onChange(newValue);\n this.onTouched();\n }\n\n /** Toggle a checkbox option (supports single and multiple selection) */\n public ToggleCheckbox(option: FormOption): void {\n if (!this.AllowMultiple) {\n this.OnValueChange(option.value);\n } else {\n const currentValues = Array.isArray(this.Value) ? this.Value : [];\n const index = currentValues.indexOf(option.value);\n const newValues = index > -1\n ? currentValues.filter((v: string | number | boolean) => v !== option.value)\n : [...currentValues, option.value];\n this.OnValueChange(newValues);\n }\n }\n\n /** Check whether a checkbox option is currently selected */\n public IsChecked(option: FormOption): boolean {\n if (!this.AllowMultiple) {\n return this.Value === option.value;\n }\n return Array.isArray(this.Value) && this.Value.includes(option.value);\n }\n\n /** Get slider configuration with defaults */\n public GetSliderConfig(): { min: number; max: number; step?: number; suffix?: string } {\n if (typeof this.Question.type === 'object' && this.Question.type.type === 'slider') {\n return {\n min: this.Question.type.min,\n max: this.Question.type.max,\n step: this.Question.type.step,\n suffix: this.Question.type.suffix\n };\n }\n return { min: 0, max: 100, step: 1 };\n }\n\n /** Handle date range start date change */\n public OnDateRangeStartChange(value: Date): void {\n const current = (this.Value as Record<string, Date>) || {};\n this.OnValueChange({ ...current, start: value });\n }\n\n /** Handle date range end date change */\n public OnDateRangeEndChange(value: Date): void {\n const current = (this.Value as Record<string, Date>) || {};\n this.OnValueChange({ ...current, end: value });\n }\n\n /** Track-by function for options lists */\n public TrackByValue(_index: number, option: FormOption): string | number | boolean {\n return option.value;\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null): void {\n this.Value = value;\n }\n\n registerOnChange(fn: (value: string | number | boolean | Date | Record<string, Date> | (string | number | boolean)[] | null) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.Disabled = isDisabled;\n }\n}\n","<div class=\"form-question\" [class.required]=\"Question.required\">\n <!-- Label -->\n @if (Question.label) {\n <label class=\"question-label\">\n {{ Question.label }}\n @if (Question.required) {\n <span class=\"required-indicator\">*</span>\n }\n </label>\n }\n\n <!-- Help Text -->\n @if (Question.helpText) {\n <div class=\"help-text\">\n {{ Question.helpText }}\n </div>\n }\n\n <!-- Input Container with width class -->\n <div class=\"input-container\" [ngClass]=\"WidthClass\">\n\n <!-- Text Input -->\n @if (QuestionType === 'text') {\n <input\n type=\"text\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Email Input -->\n @if (QuestionType === 'email') {\n <input\n type=\"email\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Textarea (markdown mode: rendered preview by default, Edit toggles the raw editor) -->\n @if (QuestionType === 'textarea') {\n @if (IsMarkdown) {\n <div class=\"markdown-field\" [class.editing]=\"MarkdownEditing\">\n <div class=\"markdown-field-toolbar\">\n <span class=\"markdown-field-hint\">\n <i class=\"fa-brands fa-markdown\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Editing Markdown' : 'Formatted preview' }}\n </span>\n <button\n type=\"button\"\n class=\"markdown-field-toggle\"\n [disabled]=\"Disabled\"\n (click)=\"ToggleMarkdownEditing()\"\n [title]=\"MarkdownEditing ? 'Show the formatted preview' : 'Edit the raw Markdown'\"\n >\n <i class=\"fa-solid\" [ngClass]=\"MarkdownEditing ? 'fa-eye' : 'fa-pen'\" aria-hidden=\"true\"></i>\n {{ MarkdownEditing ? 'Preview' : 'Edit' }}\n </button>\n </div>\n @if (!MarkdownEditing) {\n <div class=\"markdown-field-preview\">\n <mj-markdown [data]=\"MarkdownValue\"></mj-markdown>\n </div>\n }\n @if (MarkdownEditing) {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"10\"\n ></textarea>\n }\n </div>\n } @else {\n <textarea\n class=\"mj-textarea question-textarea\"\n [value]=\"$any(Value) || ''\"\n [placeholder]=\"Placeholder\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange($any($event.target).value)\"\n rows=\"5\"\n ></textarea>\n }\n }\n\n <!-- Number Input -->\n @if (QuestionType === 'number') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 1\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Currency Input -->\n @if (QuestionType === 'currency') {\n <mj-numeric-input\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Min]=\"Min || 0\"\n [Max]=\"Max || 999999999\"\n [Step]=\"Step || 0.01\"\n [Decimals]=\"2\"\n [Disabled]=\"Disabled\"\n (ngModelChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-numeric-input>\n }\n\n <!-- Date Picker -->\n @if (QuestionType === 'date') {\n <mj-datepicker\n [ngModel]=\"$any(Value)\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnValueChange($any($event))\"\n class=\"question-input\"\n ></mj-datepicker>\n }\n\n <!-- DateTime Picker -->\n @if (QuestionType === 'datetime') {\n <input\n type=\"datetime-local\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Button Group -->\n @if (QuestionType === 'buttongroup') {\n <div class=\"mj-buttongroup question-buttongroup\">\n @for (option of Options; track TrackByValue($index, option)) {\n <button\n mjButton\n [toggleable]=\"true\"\n [selected]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (click)=\"OnValueChange(option.value)\"\n class=\"buttongroup-option\"\n >\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </button>\n }\n </div>\n }\n\n <!-- Radio Buttons -->\n @if (QuestionType === 'radio') {\n <div class=\"question-radio-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"radio-option\">\n <input\n type=\"radio\"\n class=\"mj-radio\"\n [name]=\"Question.id\"\n [value]=\"option.value\"\n [checked]=\"Value === option.value\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange(option.value)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"radio-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n\n <!-- Dropdown -->\n @if (QuestionType === 'dropdown') {\n <select\n [formControl]=\"Control\"\n [disabled]=\"Disabled\"\n class=\"question-dropdown\"\n >\n <option value=\"\">Select an option...</option>\n @for (option of Options; track TrackByValue($index, option)) {\n <option [value]=\"option.value\">\n {{ option.label }}\n </option>\n }\n </select>\n }\n\n <!-- Slider -->\n @if (QuestionType === 'slider') {\n <div class=\"question-slider-container\">\n <input\n type=\"range\"\n class=\"mj-slider question-slider\"\n [min]=\"GetSliderConfig().min\"\n [max]=\"GetSliderConfig().max\"\n [step]=\"GetSliderConfig().step || 1\"\n [value]=\"$any(Value) || GetSliderConfig().min\"\n [disabled]=\"Disabled\"\n (input)=\"OnValueChange(+$any($event.target).value)\"\n />\n <div class=\"slider-value\">\n {{ Value || GetSliderConfig().min }}{{ GetSliderConfig().suffix || '' }}\n </div>\n </div>\n }\n\n <!-- Date Range -->\n @if (QuestionType === 'daterange') {\n <div class=\"question-daterange\">\n <mj-datepicker\n [ngModel]=\"$any(Value)?.start\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeStartChange($any($event))\"\n Placeholder=\"Start Date\"\n class=\"daterange-start\"\n ></mj-datepicker>\n <span class=\"daterange-separator\">to</span>\n <mj-datepicker\n [ngModel]=\"$any(Value)?.end\"\n [ngModelOptions]=\"{standalone: true}\"\n [Disabled]=\"Disabled\"\n (ValueChange)=\"OnDateRangeEndChange($any($event))\"\n Placeholder=\"End Date\"\n class=\"daterange-end\"\n ></mj-datepicker>\n </div>\n }\n\n <!-- Time Picker -->\n @if (QuestionType === 'time') {\n <input\n type=\"time\"\n class=\"mj-input question-input\"\n [value]=\"$any(Value)\"\n [disabled]=\"Disabled\"\n (change)=\"OnValueChange($any($event.target).value)\"\n />\n }\n\n <!-- Checkboxes -->\n @if (QuestionType === 'checkbox') {\n <div class=\"question-checkbox-group\">\n @for (option of Options; track TrackByValue($index, option)) {\n <div class=\"checkbox-option\">\n <input\n type=\"checkbox\"\n class=\"mj-checkbox\"\n [checked]=\"IsChecked(option)\"\n [disabled]=\"Disabled\"\n (change)=\"ToggleCheckbox(option)\"\n [id]=\"Question.id + '_' + option.value\"\n />\n <label [for]=\"Question.id + '_' + option.value\" class=\"checkbox-label\">\n @if (option.icon) {\n <i class=\"fa {{ option.icon }}\" aria-hidden=\"true\"></i>\n }\n {{ option.label }}\n </label>\n </div>\n }\n </div>\n }\n </div><!-- end input-container -->\n\n <!-- Validation Error -->\n @if (Control.invalid && Control.touched) {\n <div class=\"validation-error\">\n @if (Control.errors?.['required']) {\n <span>This field is required</span>\n }\n @if (Control.errors?.['email']) {\n <span>Please enter a valid email address</span>\n }\n @if (Control.errors?.['min']) {\n <span>Value must be at least {{ Min }}</span>\n }\n @if (Control.errors?.['max']) {\n <span>Value must be at most {{ Max }}</span>\n }\n </div>\n }\n</div>\n"]}
@@ -111,12 +111,12 @@ export class DynamicFormResponseComponent {
111
111
  TrackByKey(_index, field) {
112
112
  return field.key;
113
113
  }
114
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormResponseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
115
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: DynamicFormResponseComponent, isStandalone: false, selector: "mj-dynamic-form-response", inputs: { FormDefinition: "FormDefinition", ResponseData: "ResponseData", Title: "Title", DisplayMode: "DisplayMode" }, ngImport: i0, template: "@if (HasData) {\n <!-- Single field: inline pill -->\n @if (IsSingleField) {\n <span class=\"form-response-pill single-field\">\n <i class=\"fa fa-check\" aria-hidden=\"true\"></i>\n {{ Fields[0].displayValue }}\n </span>\n } @else {\n <!-- Multiple fields: card layout -->\n <div class=\"form-response-pill multi-field\">\n <div class=\"pill-header\">\n <i class=\"fa fa-check-square\" aria-hidden=\"true\"></i>\n {{ ResolvedTitle }}\n </div>\n <div class=\"pill-fields\">\n @for (field of Fields; track TrackByKey($index, field)) {\n <div class=\"pill-field\">\n <div class=\"field-question\">{{ field.label }}</div>\n <div class=\"field-answer\">{{ field.displayValue }}</div>\n </div>\n }\n </div>\n </div>\n }\n}\n", styles: ["/* ==================== Form Response Display ==================== */\n\n.form-response-pill {\n transition: all 0.2s ease;\n}\n\n/* Single field - inline pill */\n.form-response-pill.single-field {\n display: inline-flex;\n align-items: center;\n vertical-align: middle;\n gap: 6px;\n padding: 4px 12px;\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-radius: 16px;\n font-size: 13px;\n font-weight: 600;\n box-shadow: var(--mj-shadow-md);\n cursor: default;\n white-space: nowrap;\n}\n\n.form-response-pill.single-field i {\n font-size: 11px;\n opacity: 0.95;\n}\n\n/* Multi-field - card layout */\n.form-response-pill.multi-field {\n display: inline-block;\n max-width: min(800px, 100%);\n width: auto;\n min-width: 400px;\n margin: 8px 0;\n vertical-align: top;\n}\n\n.form-response-pill .pill-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n font-weight: 600;\n font-size: 13px;\n border-radius: 12px 12px 0 0;\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-bottom: none;\n box-shadow: var(--mj-shadow-md);\n}\n\n.form-response-pill .pill-header i {\n font-size: 12px;\n opacity: 0.95;\n}\n\n.form-response-pill .pill-fields {\n padding: 10px 12px;\n background: color-mix(in srgb, var(--mj-status-success) 8%, transparent);\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-top: none;\n border-radius: 0 0 12px 12px;\n}\n\n.form-response-pill .pill-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n font-size: 13px;\n}\n\n.form-response-pill .pill-field:not(:last-child) {\n border-bottom: 1px solid color-mix(in srgb, var(--mj-status-success) 20%, transparent);\n padding-bottom: 12px;\n margin-bottom: 0;\n}\n\n.form-response-pill .field-question {\n font-weight: 600;\n color: var(--mj-status-success);\n font-size: 12px;\n line-height: 1.4;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n opacity: 0.9;\n}\n\n.form-response-pill .field-answer {\n color: var(--mj-text-primary);\n font-weight: 400;\n font-size: 15px;\n line-height: 1.6;\n padding: 8px 0 8px 12px;\n background: transparent;\n border-left: 3px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n font-style: italic;\n}\n\n/* Hover effects */\n.form-response-pill.single-field:hover {\n transform: translateY(-1px);\n box-shadow: var(--mj-shadow-lg);\n}\n\n.form-response-pill.multi-field:hover {\n transform: translateY(-1px);\n}\n\n.form-response-pill.multi-field:hover .pill-header {\n box-shadow: var(--mj-shadow-lg);\n}\n\n/* Responsive adjustments */\n@media (max-width: 600px) {\n .form-response-pill.multi-field {\n min-width: 300px;\n }\n}\n\n@media (max-width: 400px) {\n .form-response-pill.multi-field {\n min-width: 100%;\n }\n\n .form-response-pill .field-question {\n font-size: 11px;\n }\n\n .form-response-pill .field-answer {\n font-size: 14px;\n padding: 6px 0 6px 10px;\n }\n}\n"] });
114
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormResponseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
115
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: DynamicFormResponseComponent, isStandalone: false, selector: "mj-dynamic-form-response", inputs: { FormDefinition: "FormDefinition", ResponseData: "ResponseData", Title: "Title", DisplayMode: "DisplayMode" }, ngImport: i0, template: "@if (HasData) {\n <!-- Single field: inline pill -->\n @if (IsSingleField) {\n <span class=\"form-response-pill single-field\">\n <i class=\"fa fa-check\" aria-hidden=\"true\"></i>\n {{ Fields[0].displayValue }}\n </span>\n } @else {\n <!-- Multiple fields: card layout -->\n <div class=\"form-response-pill multi-field\">\n <div class=\"pill-header\">\n <i class=\"fa fa-check-square\" aria-hidden=\"true\"></i>\n {{ ResolvedTitle }}\n </div>\n <div class=\"pill-fields\">\n @for (field of Fields; track TrackByKey($index, field)) {\n <div class=\"pill-field\">\n <div class=\"field-question\">{{ field.label }}</div>\n <div class=\"field-answer\">{{ field.displayValue }}</div>\n </div>\n }\n </div>\n </div>\n }\n}\n", styles: ["/* ==================== Form Response Display ==================== */\n\n.form-response-pill {\n transition: all 0.2s ease;\n}\n\n/* Single field - inline pill */\n.form-response-pill.single-field {\n display: inline-flex;\n align-items: flex-start;\n vertical-align: middle;\n gap: 6px;\n padding: 4px 12px;\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-radius: 16px;\n font-size: 13px;\n font-weight: 600;\n box-shadow: var(--mj-shadow-md);\n cursor: default;\n /* A chosen option can be a full sentence \u2014 wrap it instead of running past the message. */\n max-width: 100%;\n white-space: normal;\n line-height: 1.4;\n overflow-wrap: anywhere;\n}\n\n.form-response-pill.single-field i {\n font-size: 11px;\n opacity: 0.95;\n flex: 0 0 auto;\n margin-top: 3px;\n}\n\n/* Multi-field - card layout */\n.form-response-pill.multi-field {\n display: inline-block;\n max-width: min(800px, 100%);\n width: auto;\n /* min-width beats max-width, so an unconditional 400px overflows any container\n narrower than that \u2014 and the media queries below key off the viewport, not the\n message column, so a narrow chat panel on a wide screen never reaches them. */\n min-width: min(400px, 100%);\n margin: 8px 0;\n vertical-align: top;\n}\n\n.form-response-pill .pill-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n font-weight: 600;\n font-size: 13px;\n border-radius: 12px 12px 0 0;\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-bottom: none;\n box-shadow: var(--mj-shadow-md);\n}\n\n.form-response-pill .pill-header i {\n font-size: 12px;\n opacity: 0.95;\n}\n\n.form-response-pill .pill-fields {\n padding: 10px 12px;\n background: color-mix(in srgb, var(--mj-status-success) 8%, transparent);\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-top: none;\n border-radius: 0 0 12px 12px;\n}\n\n.form-response-pill .pill-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n font-size: 13px;\n}\n\n.form-response-pill .pill-field:not(:last-child) {\n border-bottom: 1px solid color-mix(in srgb, var(--mj-status-success) 20%, transparent);\n padding-bottom: 12px;\n margin-bottom: 0;\n}\n\n.form-response-pill .field-question {\n font-weight: 600;\n color: var(--mj-status-success);\n font-size: 12px;\n line-height: 1.4;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n opacity: 0.9;\n}\n\n.form-response-pill .field-answer {\n color: var(--mj-text-primary);\n font-weight: 400;\n font-size: 15px;\n line-height: 1.6;\n padding: 8px 0 8px 12px;\n background: transparent;\n border-left: 3px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n font-style: italic;\n}\n\n/* Hover effects */\n.form-response-pill.single-field:hover {\n transform: translateY(-1px);\n box-shadow: var(--mj-shadow-lg);\n}\n\n.form-response-pill.multi-field:hover {\n transform: translateY(-1px);\n}\n\n.form-response-pill.multi-field:hover .pill-header {\n box-shadow: var(--mj-shadow-lg);\n}\n\n/* Responsive adjustments */\n@media (max-width: 600px) {\n .form-response-pill.multi-field {\n min-width: min(300px, 100%);\n }\n}\n\n@media (max-width: 400px) {\n .form-response-pill.multi-field {\n min-width: 100%;\n }\n\n .form-response-pill .field-question {\n font-size: 11px;\n }\n\n .form-response-pill .field-answer {\n font-size: 14px;\n padding: 6px 0 6px 10px;\n }\n}\n"] });
116
116
  }
117
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormResponseComponent, decorators: [{
117
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormResponseComponent, decorators: [{
118
118
  type: Component,
119
- args: [{ standalone: false, selector: 'mj-dynamic-form-response', template: "@if (HasData) {\n <!-- Single field: inline pill -->\n @if (IsSingleField) {\n <span class=\"form-response-pill single-field\">\n <i class=\"fa fa-check\" aria-hidden=\"true\"></i>\n {{ Fields[0].displayValue }}\n </span>\n } @else {\n <!-- Multiple fields: card layout -->\n <div class=\"form-response-pill multi-field\">\n <div class=\"pill-header\">\n <i class=\"fa fa-check-square\" aria-hidden=\"true\"></i>\n {{ ResolvedTitle }}\n </div>\n <div class=\"pill-fields\">\n @for (field of Fields; track TrackByKey($index, field)) {\n <div class=\"pill-field\">\n <div class=\"field-question\">{{ field.label }}</div>\n <div class=\"field-answer\">{{ field.displayValue }}</div>\n </div>\n }\n </div>\n </div>\n }\n}\n", styles: ["/* ==================== Form Response Display ==================== */\n\n.form-response-pill {\n transition: all 0.2s ease;\n}\n\n/* Single field - inline pill */\n.form-response-pill.single-field {\n display: inline-flex;\n align-items: center;\n vertical-align: middle;\n gap: 6px;\n padding: 4px 12px;\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-radius: 16px;\n font-size: 13px;\n font-weight: 600;\n box-shadow: var(--mj-shadow-md);\n cursor: default;\n white-space: nowrap;\n}\n\n.form-response-pill.single-field i {\n font-size: 11px;\n opacity: 0.95;\n}\n\n/* Multi-field - card layout */\n.form-response-pill.multi-field {\n display: inline-block;\n max-width: min(800px, 100%);\n width: auto;\n min-width: 400px;\n margin: 8px 0;\n vertical-align: top;\n}\n\n.form-response-pill .pill-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n font-weight: 600;\n font-size: 13px;\n border-radius: 12px 12px 0 0;\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-bottom: none;\n box-shadow: var(--mj-shadow-md);\n}\n\n.form-response-pill .pill-header i {\n font-size: 12px;\n opacity: 0.95;\n}\n\n.form-response-pill .pill-fields {\n padding: 10px 12px;\n background: color-mix(in srgb, var(--mj-status-success) 8%, transparent);\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-top: none;\n border-radius: 0 0 12px 12px;\n}\n\n.form-response-pill .pill-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n font-size: 13px;\n}\n\n.form-response-pill .pill-field:not(:last-child) {\n border-bottom: 1px solid color-mix(in srgb, var(--mj-status-success) 20%, transparent);\n padding-bottom: 12px;\n margin-bottom: 0;\n}\n\n.form-response-pill .field-question {\n font-weight: 600;\n color: var(--mj-status-success);\n font-size: 12px;\n line-height: 1.4;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n opacity: 0.9;\n}\n\n.form-response-pill .field-answer {\n color: var(--mj-text-primary);\n font-weight: 400;\n font-size: 15px;\n line-height: 1.6;\n padding: 8px 0 8px 12px;\n background: transparent;\n border-left: 3px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n font-style: italic;\n}\n\n/* Hover effects */\n.form-response-pill.single-field:hover {\n transform: translateY(-1px);\n box-shadow: var(--mj-shadow-lg);\n}\n\n.form-response-pill.multi-field:hover {\n transform: translateY(-1px);\n}\n\n.form-response-pill.multi-field:hover .pill-header {\n box-shadow: var(--mj-shadow-lg);\n}\n\n/* Responsive adjustments */\n@media (max-width: 600px) {\n .form-response-pill.multi-field {\n min-width: 300px;\n }\n}\n\n@media (max-width: 400px) {\n .form-response-pill.multi-field {\n min-width: 100%;\n }\n\n .form-response-pill .field-question {\n font-size: 11px;\n }\n\n .form-response-pill .field-answer {\n font-size: 14px;\n padding: 6px 0 6px 10px;\n }\n}\n"] }]
119
+ args: [{ standalone: false, selector: 'mj-dynamic-form-response', template: "@if (HasData) {\n <!-- Single field: inline pill -->\n @if (IsSingleField) {\n <span class=\"form-response-pill single-field\">\n <i class=\"fa fa-check\" aria-hidden=\"true\"></i>\n {{ Fields[0].displayValue }}\n </span>\n } @else {\n <!-- Multiple fields: card layout -->\n <div class=\"form-response-pill multi-field\">\n <div class=\"pill-header\">\n <i class=\"fa fa-check-square\" aria-hidden=\"true\"></i>\n {{ ResolvedTitle }}\n </div>\n <div class=\"pill-fields\">\n @for (field of Fields; track TrackByKey($index, field)) {\n <div class=\"pill-field\">\n <div class=\"field-question\">{{ field.label }}</div>\n <div class=\"field-answer\">{{ field.displayValue }}</div>\n </div>\n }\n </div>\n </div>\n }\n}\n", styles: ["/* ==================== Form Response Display ==================== */\n\n.form-response-pill {\n transition: all 0.2s ease;\n}\n\n/* Single field - inline pill */\n.form-response-pill.single-field {\n display: inline-flex;\n align-items: flex-start;\n vertical-align: middle;\n gap: 6px;\n padding: 4px 12px;\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-radius: 16px;\n font-size: 13px;\n font-weight: 600;\n box-shadow: var(--mj-shadow-md);\n cursor: default;\n /* A chosen option can be a full sentence \u2014 wrap it instead of running past the message. */\n max-width: 100%;\n white-space: normal;\n line-height: 1.4;\n overflow-wrap: anywhere;\n}\n\n.form-response-pill.single-field i {\n font-size: 11px;\n opacity: 0.95;\n flex: 0 0 auto;\n margin-top: 3px;\n}\n\n/* Multi-field - card layout */\n.form-response-pill.multi-field {\n display: inline-block;\n max-width: min(800px, 100%);\n width: auto;\n /* min-width beats max-width, so an unconditional 400px overflows any container\n narrower than that \u2014 and the media queries below key off the viewport, not the\n message column, so a narrow chat panel on a wide screen never reaches them. */\n min-width: min(400px, 100%);\n margin: 8px 0;\n vertical-align: top;\n}\n\n.form-response-pill .pill-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n font-weight: 600;\n font-size: 13px;\n border-radius: 12px 12px 0 0;\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-bottom: none;\n box-shadow: var(--mj-shadow-md);\n}\n\n.form-response-pill .pill-header i {\n font-size: 12px;\n opacity: 0.95;\n}\n\n.form-response-pill .pill-fields {\n padding: 10px 12px;\n background: color-mix(in srgb, var(--mj-status-success) 8%, transparent);\n border: 2px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n border-top: none;\n border-radius: 0 0 12px 12px;\n}\n\n.form-response-pill .pill-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n font-size: 13px;\n}\n\n.form-response-pill .pill-field:not(:last-child) {\n border-bottom: 1px solid color-mix(in srgb, var(--mj-status-success) 20%, transparent);\n padding-bottom: 12px;\n margin-bottom: 0;\n}\n\n.form-response-pill .field-question {\n font-weight: 600;\n color: var(--mj-status-success);\n font-size: 12px;\n line-height: 1.4;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n opacity: 0.9;\n}\n\n.form-response-pill .field-answer {\n color: var(--mj-text-primary);\n font-weight: 400;\n font-size: 15px;\n line-height: 1.6;\n padding: 8px 0 8px 12px;\n background: transparent;\n border-left: 3px solid color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n font-style: italic;\n}\n\n/* Hover effects */\n.form-response-pill.single-field:hover {\n transform: translateY(-1px);\n box-shadow: var(--mj-shadow-lg);\n}\n\n.form-response-pill.multi-field:hover {\n transform: translateY(-1px);\n}\n\n.form-response-pill.multi-field:hover .pill-header {\n box-shadow: var(--mj-shadow-lg);\n}\n\n/* Responsive adjustments */\n@media (max-width: 600px) {\n .form-response-pill.multi-field {\n min-width: min(300px, 100%);\n }\n}\n\n@media (max-width: 400px) {\n .form-response-pill.multi-field {\n min-width: 100%;\n }\n\n .form-response-pill .field-question {\n font-size: 11px;\n }\n\n .form-response-pill .field-answer {\n font-size: 14px;\n padding: 6px 0 6px 10px;\n }\n}\n"] }]
120
120
  }], propDecorators: { FormDefinition: [{
121
121
  type: Input
122
122
  }], ResponseData: [{
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-form-response.component.js","sourceRoot":"","sources":["../../../src/lib/dynamic-form-response/dynamic-form-response.component.ts","../../../src/lib/dynamic-form-response/dynamic-form-response.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Dynamic Form Response Component\n *\n * Read-only companion to `DynamicFormComponent`. Renders a submitted form\n * response as a styled pill (single field) or card (multiple fields) with\n * type-aware value formatting.\n *\n * Accepts two input modes:\n * - **Schema + ResponseData**: Pass the original `AgentResponseForm` schema\n * plus the JSON response data to get full labels and type-aware formatting.\n * - **ResponseData only**: Pass just the JSON response data for a simpler\n * key/value display when the schema is unavailable.\n *\n * @example With schema\n * ```html\n * <mj-dynamic-form-response\n * [FormDefinition]=\"responseForm\"\n * [ResponseData]=\"responseDataJson\">\n * </mj-dynamic-form-response>\n * ```\n *\n * @example Without schema\n * ```html\n * <mj-dynamic-form-response\n * [ResponseData]=\"responseDataJson\">\n * </mj-dynamic-form-response>\n * ```\n *\n * @module @memberjunction/ng-forms\n */\n\nimport { Component, Input } from '@angular/core';\nimport { AgentResponseForm, FormQuestion } from '@memberjunction/ai-core-plus';\nimport { FormResponseField, FormResponseUtils } from './form-response-utils';\n\n@Component({\n standalone: false,\n selector: 'mj-dynamic-form-response',\n templateUrl: './dynamic-form-response.component.html',\n styleUrls: ['./dynamic-form-response.component.css']\n})\nexport class DynamicFormResponseComponent {\n /**\n * Optional form schema for resolving labels, types, and display values.\n * When provided, enables type-aware formatting and option label resolution.\n */\n @Input() FormDefinition: AgentResponseForm | null = null;\n\n /**\n * The response data as a JSON string or pre-parsed object.\n * Keys are question IDs, values are the user's responses.\n */\n @Input() ResponseData: string | Record<string, unknown> | null = null;\n\n /**\n * Optional title override. Falls back to FormDefinition.title, then 'Form Response'.\n */\n @Input() Title: string | null = null;\n\n /**\n * Display mode: 'pill' for inline badge style, 'card' for block card style,\n * 'auto' to choose based on field count (default).\n */\n @Input() DisplayMode: 'pill' | 'card' | 'auto' = 'auto';\n\n /** Resolved fields ready for display */\n public get Fields(): FormResponseField[] {\n const data = this.parsedResponseData;\n if (!data) return [];\n\n const entries = Object.entries(data).filter(\n ([, value]) => value != null && value !== '' && !(Array.isArray(value) && value.length === 0)\n );\n\n return entries.map(([key, value]) => this.resolveField(key, value));\n }\n\n /** Whether this displays as a single-field inline pill */\n public get IsSingleField(): boolean {\n if (this.DisplayMode === 'card') return false;\n if (this.DisplayMode === 'pill') return true;\n return this.Fields.length === 1;\n }\n\n /** Whether there is any data to display */\n public get HasData(): boolean {\n return this.Fields.length > 0;\n }\n\n /** Resolved title for multi-field display */\n public get ResolvedTitle(): string {\n return this.Title || this.FormDefinition?.title || 'Form Response';\n }\n\n /** Parse the ResponseData input into a plain object */\n private get parsedResponseData(): Record<string, unknown> | null {\n if (!this.ResponseData) return null;\n if (typeof this.ResponseData === 'object') return this.ResponseData;\n try {\n return JSON.parse(this.ResponseData) as Record<string, unknown>;\n } catch {\n return null;\n }\n }\n\n /** Resolve a single key/value pair into a display-ready field */\n private resolveField(key: string, value: unknown): FormResponseField {\n const question = this.findQuestion(key);\n const type = question ? FormResponseUtils.GetQuestionTypeString(question) : null;\n const displayValue = FormResponseUtils.FormatValue(value, question, type);\n\n return {\n key,\n label: question?.label || FormResponseUtils.HumanizeKey(key),\n value,\n displayValue,\n type\n };\n }\n\n /** Find the matching question in the schema */\n private findQuestion(id: string): FormQuestion | null {\n if (!this.FormDefinition) return null;\n return this.FormDefinition.questions.find(q => q.id === id) || null;\n }\n\n /** Track-by function for fields */\n public TrackByKey(_index: number, field: FormResponseField): string {\n return field.key;\n }\n}\n","@if (HasData) {\n <!-- Single field: inline pill -->\n @if (IsSingleField) {\n <span class=\"form-response-pill single-field\">\n <i class=\"fa fa-check\" aria-hidden=\"true\"></i>\n {{ Fields[0].displayValue }}\n </span>\n } @else {\n <!-- Multiple fields: card layout -->\n <div class=\"form-response-pill multi-field\">\n <div class=\"pill-header\">\n <i class=\"fa fa-check-square\" aria-hidden=\"true\"></i>\n {{ ResolvedTitle }}\n </div>\n <div class=\"pill-fields\">\n @for (field of Fields; track TrackByKey($index, field)) {\n <div class=\"pill-field\">\n <div class=\"field-question\">{{ field.label }}</div>\n <div class=\"field-answer\">{{ field.displayValue }}</div>\n </div>\n }\n </div>\n </div>\n }\n}\n"]}
1
+ {"version":3,"file":"dynamic-form-response.component.js","sourceRoot":"","sources":["../../../src/lib/dynamic-form-response/dynamic-form-response.component.ts","../../../src/lib/dynamic-form-response/dynamic-form-response.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Dynamic Form Response Component\n *\n * Read-only companion to `DynamicFormComponent`. Renders a submitted form\n * response as a styled pill (single field) or card (multiple fields) with\n * type-aware value formatting.\n *\n * Accepts two input modes:\n * - **Schema + ResponseData**: Pass the original `AgentResponseForm` schema\n * plus the JSON response data to get full labels and type-aware formatting.\n * - **ResponseData only**: Pass just the JSON response data for a simpler\n * key/value display when the schema is unavailable.\n *\n * @example With schema\n * ```html\n * <mj-dynamic-form-response\n * [FormDefinition]=\"responseForm\"\n * [ResponseData]=\"responseDataJson\">\n * </mj-dynamic-form-response>\n * ```\n *\n * @example Without schema\n * ```html\n * <mj-dynamic-form-response\n * [ResponseData]=\"responseDataJson\">\n * </mj-dynamic-form-response>\n * ```\n *\n * @module @memberjunction/ng-forms\n */\n\nimport { Component, Input } from '@angular/core';\nimport { AgentResponseForm, FormQuestion } from '@memberjunction/ai-core-plus';\nimport { FormResponseField, FormResponseUtils } from './form-response-utils';\n\n@Component({\n standalone: false,\n selector: 'mj-dynamic-form-response',\n templateUrl: './dynamic-form-response.component.html',\n styleUrls: ['./dynamic-form-response.component.css']\n})\nexport class DynamicFormResponseComponent {\n /**\n * Optional form schema for resolving labels, types, and display values.\n * When provided, enables type-aware formatting and option label resolution.\n */\n @Input() FormDefinition: AgentResponseForm | null = null;\n\n /**\n * The response data as a JSON string or pre-parsed object.\n * Keys are question IDs, values are the user's responses.\n */\n @Input() ResponseData: string | Record<string, unknown> | null = null;\n\n /**\n * Optional title override. Falls back to FormDefinition.title, then 'Form Response'.\n */\n @Input() Title: string | null = null;\n\n /**\n * Display mode: 'pill' for inline badge style, 'card' for block card style,\n * 'auto' to choose based on field count (default).\n */\n @Input() DisplayMode: 'pill' | 'card' | 'auto' = 'auto';\n\n /** Resolved fields ready for display */\n public get Fields(): FormResponseField[] {\n const data = this.parsedResponseData;\n if (!data) return [];\n\n const entries = Object.entries(data).filter(\n ([, value]) => value != null && value !== '' && !(Array.isArray(value) && value.length === 0)\n );\n\n return entries.map(([key, value]) => this.resolveField(key, value));\n }\n\n /** Whether this displays as a single-field inline pill */\n public get IsSingleField(): boolean {\n if (this.DisplayMode === 'card') return false;\n if (this.DisplayMode === 'pill') return true;\n return this.Fields.length === 1;\n }\n\n /** Whether there is any data to display */\n public get HasData(): boolean {\n return this.Fields.length > 0;\n }\n\n /** Resolved title for multi-field display */\n public get ResolvedTitle(): string {\n return this.Title || this.FormDefinition?.title || 'Form Response';\n }\n\n /** Parse the ResponseData input into a plain object */\n private get parsedResponseData(): Record<string, unknown> | null {\n if (!this.ResponseData) return null;\n if (typeof this.ResponseData === 'object') return this.ResponseData;\n try {\n return JSON.parse(this.ResponseData) as Record<string, unknown>;\n } catch {\n return null;\n }\n }\n\n /** Resolve a single key/value pair into a display-ready field */\n private resolveField(key: string, value: unknown): FormResponseField {\n const question = this.findQuestion(key);\n const type = question ? FormResponseUtils.GetQuestionTypeString(question) : null;\n const displayValue = FormResponseUtils.FormatValue(value, question, type);\n\n return {\n key,\n label: question?.label || FormResponseUtils.HumanizeKey(key),\n value,\n displayValue,\n type\n };\n }\n\n /** Find the matching question in the schema */\n private findQuestion(id: string): FormQuestion | null {\n if (!this.FormDefinition) return null;\n return this.FormDefinition.questions.find(q => q.id === id) || null;\n }\n\n /** Track-by function for fields */\n public TrackByKey(_index: number, field: FormResponseField): string {\n return field.key;\n }\n}\n","@if (HasData) {\n <!-- Single field: inline pill -->\n @if (IsSingleField) {\n <span class=\"form-response-pill single-field\">\n <i class=\"fa fa-check\" aria-hidden=\"true\"></i>\n {{ Fields[0].displayValue }}\n </span>\n } @else {\n <!-- Multiple fields: card layout -->\n <div class=\"form-response-pill multi-field\">\n <div class=\"pill-header\">\n <i class=\"fa fa-check-square\" aria-hidden=\"true\"></i>\n {{ ResolvedTitle }}\n </div>\n <div class=\"pill-fields\">\n @for (field of Fields; track TrackByKey($index, field)) {\n <div class=\"pill-field\">\n <div class=\"field-question\">{{ field.label }}</div>\n <div class=\"field-answer\">{{ field.displayValue }}</div>\n </div>\n }\n </div>\n </div>\n }\n}\n"]}
@@ -52,8 +52,8 @@ import { DynamicFormFieldComponent } from './dynamic-form-field/dynamic-form-fie
52
52
  import { DynamicFormResponseComponent } from './dynamic-form-response/dynamic-form-response.component';
53
53
  import * as i0 from "@angular/core";
54
54
  export class DynamicFormsModule {
55
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
56
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormsModule, declarations: [DynamicFormComponent,
55
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
56
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormsModule, declarations: [DynamicFormComponent,
57
57
  DynamicFormFieldComponent,
58
58
  DynamicFormResponseComponent], imports: [CommonModule,
59
59
  FormsModule,
@@ -64,14 +64,14 @@ export class DynamicFormsModule {
64
64
  MarkdownModule], exports: [DynamicFormComponent,
65
65
  DynamicFormFieldComponent,
66
66
  DynamicFormResponseComponent] });
67
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormsModule, imports: [CommonModule,
67
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormsModule, imports: [CommonModule,
68
68
  FormsModule,
69
69
  ReactiveFormsModule,
70
70
  MJNumericInputComponent,
71
71
  MJDatepickerComponent,
72
72
  MarkdownModule] });
73
73
  }
74
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormsModule, decorators: [{
74
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormsModule, decorators: [{
75
75
  type: NgModule,
76
76
  args: [{
77
77
  declarations: [
@@ -1 +1 @@
1
- {"version":3,"file":"forms.module.js","sourceRoot":"","sources":["../../src/lib/forms.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAElE,mBAAmB;AACnB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACrH,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,aAAa;AACb,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,mDAAmD,CAAC;AAC9F,OAAO,EAAE,4BAA4B,EAAE,MAAM,yDAAyD,CAAC;;AAuBvG,MAAM,OAAO,kBAAkB;uGAAlB,kBAAkB;wGAAlB,kBAAkB,iBAnBvB,oBAAoB;YACpB,yBAAyB;YACzB,4BAA4B,aAG5B,YAAY;YACZ,WAAW;YACX,mBAAmB;YACnB,iBAAiB;YACjB,uBAAuB;YACvB,qBAAqB;YACrB,cAAc,aAGd,oBAAoB;YACpB,yBAAyB;YACzB,4BAA4B;wGAGvB,kBAAkB,YAdvB,YAAY;YACZ,WAAW;YACX,mBAAmB;YAEnB,uBAAuB;YACvB,qBAAqB;YACrB,cAAc;;2FAQT,kBAAkB;kBArB9B,QAAQ;mBAAC;oBACN,YAAY,EAAE;wBACV,oBAAoB;wBACpB,yBAAyB;wBACzB,4BAA4B;qBAC/B;oBACD,OAAO,EAAE;wBACL,YAAY;wBACZ,WAAW;wBACX,mBAAmB;wBACnB,iBAAiB;wBACjB,uBAAuB;wBACvB,qBAAqB;wBACrB,cAAc;qBACjB;oBACD,OAAO,EAAE;wBACL,oBAAoB;wBACpB,yBAAyB;wBACzB,4BAA4B;qBAC/B;iBACJ","sourcesContent":["/**\n * @fileoverview MJ Dynamic Forms Module\n *\n * Provides reusable dynamic form components that render structured forms from\n * `AgentResponseForm` JSON schemas. Supports 14+ input types with built-in\n * validation, responsive layout, and intelligent field sizing.\n *\n * ## Usage\n *\n * Import `DynamicFormsModule` into your feature module:\n *\n * ```typescript\n * import { DynamicFormsModule } from '@memberjunction/ng-forms';\n *\n * @NgModule({\n * imports: [DynamicFormsModule]\n * })\n * export class MyFeatureModule { }\n * ```\n *\n * Then use in templates:\n *\n * ```html\n * <mj-dynamic-form\n * [FormDefinition]=\"myFormSchema\"\n * [Disabled]=\"isSaving\"\n * (FormSubmitted)=\"onSubmit($event)\">\n * </mj-dynamic-form>\n * ```\n *\n * Or use the field component individually within your own reactive forms:\n *\n * ```html\n * <mj-dynamic-form-field\n * [Question]=\"question\"\n * [Control]=\"myFormGroup.get(question.id)\"\n * [formControlName]=\"question.id\">\n * </mj-dynamic-form-field>\n * ```\n *\n * @module @memberjunction/ng-forms\n */\n\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n// MJ UI components\nimport { MJButtonDirective, MJNumericInputComponent, MJDatepickerComponent } from '@memberjunction/ng-ui-components';\nimport { MarkdownModule } from '@memberjunction/ng-markdown';\n\n// Components\nimport { DynamicFormComponent } from './dynamic-form/dynamic-form.component';\nimport { DynamicFormFieldComponent } from './dynamic-form-field/dynamic-form-field.component';\nimport { DynamicFormResponseComponent } from './dynamic-form-response/dynamic-form-response.component';\n\n@NgModule({\n declarations: [\n DynamicFormComponent,\n DynamicFormFieldComponent,\n DynamicFormResponseComponent\n ],\n imports: [\n CommonModule,\n FormsModule,\n ReactiveFormsModule,\n MJButtonDirective,\n MJNumericInputComponent,\n MJDatepickerComponent,\n MarkdownModule\n ],\n exports: [\n DynamicFormComponent,\n DynamicFormFieldComponent,\n DynamicFormResponseComponent\n ]\n})\nexport class DynamicFormsModule { }\n"]}
1
+ {"version":3,"file":"forms.module.js","sourceRoot":"","sources":["../../src/lib/forms.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAElE,mBAAmB;AACnB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACrH,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,aAAa;AACb,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,mDAAmD,CAAC;AAC9F,OAAO,EAAE,4BAA4B,EAAE,MAAM,yDAAyD,CAAC;;AAuBvG,MAAM,OAAO,kBAAkB;wGAAlB,kBAAkB;yGAAlB,kBAAkB,iBAnBvB,oBAAoB;YACpB,yBAAyB;YACzB,4BAA4B,aAG5B,YAAY;YACZ,WAAW;YACX,mBAAmB;YACnB,iBAAiB;YACjB,uBAAuB;YACvB,qBAAqB;YACrB,cAAc,aAGd,oBAAoB;YACpB,yBAAyB;YACzB,4BAA4B;yGAGvB,kBAAkB,YAdvB,YAAY;YACZ,WAAW;YACX,mBAAmB;YAEnB,uBAAuB;YACvB,qBAAqB;YACrB,cAAc;;4FAQT,kBAAkB;kBArB9B,QAAQ;mBAAC;oBACN,YAAY,EAAE;wBACV,oBAAoB;wBACpB,yBAAyB;wBACzB,4BAA4B;qBAC/B;oBACD,OAAO,EAAE;wBACL,YAAY;wBACZ,WAAW;wBACX,mBAAmB;wBACnB,iBAAiB;wBACjB,uBAAuB;wBACvB,qBAAqB;wBACrB,cAAc;qBACjB;oBACD,OAAO,EAAE;wBACL,oBAAoB;wBACpB,yBAAyB;wBACzB,4BAA4B;qBAC/B;iBACJ","sourcesContent":["/**\n * @fileoverview MJ Dynamic Forms Module\n *\n * Provides reusable dynamic form components that render structured forms from\n * `AgentResponseForm` JSON schemas. Supports 14+ input types with built-in\n * validation, responsive layout, and intelligent field sizing.\n *\n * ## Usage\n *\n * Import `DynamicFormsModule` into your feature module:\n *\n * ```typescript\n * import { DynamicFormsModule } from '@memberjunction/ng-forms';\n *\n * @NgModule({\n * imports: [DynamicFormsModule]\n * })\n * export class MyFeatureModule { }\n * ```\n *\n * Then use in templates:\n *\n * ```html\n * <mj-dynamic-form\n * [FormDefinition]=\"myFormSchema\"\n * [Disabled]=\"isSaving\"\n * (FormSubmitted)=\"onSubmit($event)\">\n * </mj-dynamic-form>\n * ```\n *\n * Or use the field component individually within your own reactive forms:\n *\n * ```html\n * <mj-dynamic-form-field\n * [Question]=\"question\"\n * [Control]=\"myFormGroup.get(question.id)\"\n * [formControlName]=\"question.id\">\n * </mj-dynamic-form-field>\n * ```\n *\n * @module @memberjunction/ng-forms\n */\n\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n// MJ UI components\nimport { MJButtonDirective, MJNumericInputComponent, MJDatepickerComponent } from '@memberjunction/ng-ui-components';\nimport { MarkdownModule } from '@memberjunction/ng-markdown';\n\n// Components\nimport { DynamicFormComponent } from './dynamic-form/dynamic-form.component';\nimport { DynamicFormFieldComponent } from './dynamic-form-field/dynamic-form-field.component';\nimport { DynamicFormResponseComponent } from './dynamic-form-response/dynamic-form-response.component';\n\n@NgModule({\n declarations: [\n DynamicFormComponent,\n DynamicFormFieldComponent,\n DynamicFormResponseComponent\n ],\n imports: [\n CommonModule,\n FormsModule,\n ReactiveFormsModule,\n MJButtonDirective,\n MJNumericInputComponent,\n MJDatepickerComponent,\n MarkdownModule\n ],\n exports: [\n DynamicFormComponent,\n DynamicFormFieldComponent,\n DynamicFormResponseComponent\n ]\n})\nexport class DynamicFormsModule { }\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-forms",
3
- "version": "6.1.0-edge.4",
3
+ "version": "6.1.0-edge.6",
4
4
  "description": "MemberJunction: Generic dynamic form rendering from JSON schemas. Renders structured forms with 14+ field types (text, textarea, number, currency, date, daterange, time, checkbox, radio, dropdown, buttongroup, slider, email) with built-in validation, responsive layout, and intelligent field sizing.",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -18,20 +18,20 @@
18
18
  "author": "",
19
19
  "license": "BUSL-1.1",
20
20
  "devDependencies": {
21
- "@angular/compiler": "21.1.3",
22
- "@angular/compiler-cli": "21.1.3",
23
- "@memberjunction/ng-test-utils": "6.1.0-edge.4",
21
+ "@angular/compiler": "21.2.22",
22
+ "@angular/compiler-cli": "21.2.22",
23
+ "@memberjunction/ng-test-utils": "6.1.0-edge.6",
24
24
  "vitest": "^4.0.18"
25
25
  },
26
26
  "peerDependencies": {
27
- "@angular/common": "^21.1.3",
28
- "@angular/core": "^21.1.3",
29
- "@angular/forms": "^21.1.3"
27
+ "@angular/common": "^21.2.22",
28
+ "@angular/core": "^21.2.22",
29
+ "@angular/forms": "^21.2.22"
30
30
  },
31
31
  "dependencies": {
32
- "@memberjunction/ai-core-plus": "6.1.0-edge.4",
33
- "@memberjunction/ng-markdown": "6.1.0-edge.4",
34
- "@memberjunction/ng-ui-components": "6.1.0-edge.4",
32
+ "@memberjunction/ai-core-plus": "6.1.0-edge.6",
33
+ "@memberjunction/ng-markdown": "6.1.0-edge.6",
34
+ "@memberjunction/ng-ui-components": "6.1.0-edge.6",
35
35
  "tslib": "^2.8.1"
36
36
  },
37
37
  "sideEffects": false,