@memberjunction/ng-forms 6.1.4 → 6.2.0-edge.1

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.
@@ -241,7 +241,7 @@ export class DynamicFormComponent {
241
241
  this.FormGroup = new FormGroup(controls);
242
242
  }
243
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"] }] });
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", "variant", "Size", "size", "Toggleable", "toggleable", "Selected", "selected", "AriaLabel", "ariaLabel"], outputs: ["SelectedChange", "selectedChange"] }, { kind: "component", type: i3.DynamicFormFieldComponent, selector: "mj-dynamic-form-field", inputs: ["Question", "Control"] }] });
245
245
  }
246
246
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormComponent, decorators: [{
247
247
  type: Component,
@@ -241,7 +241,7 @@ export class DynamicFormFieldComponent {
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/* 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"] }] });
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", "variant", "Size", "size", "Toggleable", "toggleable", "Selected", "selected", "AriaLabel", "ariaLabel"], outputs: ["SelectedChange", "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", "data", "EnableHighlight", "enableHighlight", "EnableMermaid", "enableMermaid", "EnableCodeCopy", "enableCodeCopy", "EnableCollapsibleHeadings", "enableCollapsibleHeadings", "CollapsibleHeadingLevel", "collapsibleHeadingLevel", "CollapsibleDefaultExpanded", "collapsibleDefaultExpanded", "AutoExpandLevels", "autoExpandLevels", "EnableAlerts", "enableAlerts", "EnableSmartypants", "enableSmartypants", "EnableSvgRenderer", "enableSvgRenderer", "EnableHtml", "enableHtml", "EnableJavaScript", "enableJavaScript", "EnableHeadingIds", "enableHeadingIds", "HeadingIdPrefix", "headingIdPrefix", "EnableLineNumbers", "enableLineNumbers", "ContainerClass", "containerClass", "MermaidTheme", "mermaidTheme", "Sanitize", "sanitize"], outputs: ["Rendered", "rendered", "HeadingClick", "headingClick", "CodeCopied", "codeCopied"] }] });
245
245
  }
246
246
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormFieldComponent, decorators: [{
247
247
  type: Component,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-forms",
3
- "version": "6.1.4",
3
+ "version": "6.2.0-edge.1",
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",
@@ -20,7 +20,7 @@
20
20
  "devDependencies": {
21
21
  "@angular/compiler": "21.2.22",
22
22
  "@angular/compiler-cli": "21.2.22",
23
- "@memberjunction/ng-test-utils": "6.1.4",
23
+ "@memberjunction/ng-test-utils": "6.2.0-edge.1",
24
24
  "vitest": "^4.0.18"
25
25
  },
26
26
  "peerDependencies": {
@@ -29,9 +29,9 @@
29
29
  "@angular/forms": "^21.2.22"
30
30
  },
31
31
  "dependencies": {
32
- "@memberjunction/ai-core-plus": "6.1.4",
33
- "@memberjunction/ng-markdown": "6.1.4",
34
- "@memberjunction/ng-ui-components": "6.1.4",
32
+ "@memberjunction/ai-core-plus": "6.2.0-edge.1",
33
+ "@memberjunction/ng-markdown": "6.2.0-edge.1",
34
+ "@memberjunction/ng-ui-components": "6.2.0-edge.1",
35
35
  "tslib": "^2.8.1"
36
36
  },
37
37
  "sideEffects": false,