@memberjunction/ng-forms 5.51.1 → 5.51.3
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,11 +241,11 @@ export class DynamicFormComponent {
|
|
|
241
241
|
this.FormGroup = new FormGroup(controls);
|
|
242
242
|
}
|
|
243
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"] }] });
|
|
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: ["/* NOTE (lts/5): clip-fix subset of #4085 only \u2014 that PR also widened this card to\n 720px, which stays on `next`. See the matching note in dynamic-form-field.component.css. */\n.dynamic-form {\n margin-top: 16px;\n display: block;\n width: fit-content;\n /* as a flex item the card would otherwise refuse to shrink below its content and\n overflow a narrow message column; inert everywhere else */\n min-width: 0;\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"] }] });
|
|
245
245
|
}
|
|
246
246
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", 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: ["/* NOTE (lts/5): clip-fix subset of #4085 only \u2014 that PR also widened this card to\n 720px, which stays on `next`. See the matching note in dynamic-form-field.component.css. */\n.dynamic-form {\n margin-top: 16px;\n display: block;\n width: fit-content;\n /* as a flex item the card would otherwise refuse to shrink below its content and\n overflow a narrow message column; inert everywhere else */\n min-width: 0;\n max-width: 600px;\n}\n\n/* Simple Choice Mode */\n.simple-choice-buttons {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 12px;\n}\n\n.choice-button {\n padding: 8px 16px;\n font-size: 14px;\n}\n\n.choice-button i,\n.choice-button i.fa-solid {\n margin-right: 6px;\n}\n\n.choice-button.selected {\n box-shadow: 0 0 0 2px var(--mj-brand-primary);\n}\n\n/* Full Form Mode */\n.response-form {\n background: color-mix(in srgb, var(--mj-brand-primary) 3%, transparent);\n border: 2px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n border-radius: 12px;\n padding: 0;\n margin-top: 12px;\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n overflow: hidden;\n}\n\n.form-title {\n margin: 0;\n padding: 12px 16px;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-inverse);\n background: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.form-title::before {\n content: \"\\f044\";\n font-family: \"Font Awesome 6 Free\";\n font-weight: 900;\n font-size: 13px;\n opacity: 0.95;\n}\n\n.form-description {\n margin: 0;\n padding: 12px 16px;\n font-size: 13px;\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n border-bottom: 1px solid color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n font-style: italic;\n}\n\n.form-questions {\n padding: 16px;\n background: var(--mj-bg-surface-card);\n}\n\n.form-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: flex-start;\n padding: 12px 16px;\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, transparent);\n border-top: 1px solid color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.submit-button {\n min-width: 120px;\n}\n\n/* Footer choice buttons - horizontal on desktop with text wrapping */\n.footer-choice-button {\n min-width: 120px;\n max-width: 100%;\n white-space: normal;\n text-align: left;\n line-height: 1.3;\n padding: 10px 16px;\n}\n\n.footer-choice-button i.fa,\n.footer-choice-button i.fa-solid {\n margin-right: 6px;\n flex-shrink: 0;\n}\n\n.footer-choice-button.selected {\n box-shadow: 0 0 0 2px var(--mj-brand-primary);\n}\n\n.submit-button i.fa {\n margin-right: 6px;\n}\n\n/* Responsive Design */\n@media (max-width: 768px) {\n .dynamic-form {\n margin-top: 12px;\n }\n\n .response-form {\n border-radius: 10px;\n }\n\n .form-questions {\n padding: 12px;\n }\n\n .form-actions {\n padding: 10px 12px;\n }\n\n .simple-choice-buttons {\n gap: 8px;\n }\n\n .choice-button {\n padding: 10px 14px;\n font-size: 13px;\n }\n}\n\n@media (max-width: 480px) {\n .dynamic-form {\n margin-top: 10px;\n width: 100%;\n max-width: 100%;\n }\n\n .response-form {\n border-radius: 8px;\n border-width: 1.5px;\n }\n\n .form-title {\n font-size: 13px;\n padding: 10px 12px;\n }\n\n .form-description {\n font-size: 12px;\n padding: 10px 12px;\n }\n\n .form-questions {\n padding: 10px;\n }\n\n .form-actions {\n padding: 8px 10px;\n flex-direction: column;\n }\n\n .choice-button {\n width: 100%;\n min-height: 44px;\n padding: 12px 16px;\n font-size: 14px;\n }\n\n .footer-choice-button {\n width: 100%;\n min-height: 44px;\n font-size: 14px;\n }\n\n .submit-button {\n width: 100%;\n min-height: 44px;\n font-size: 13px;\n }\n}\n"] }]
|
|
249
249
|
}], propDecorators: { FormDefinition: [{
|
|
250
250
|
type: Input
|
|
251
251
|
}], Disabled: [{
|
|
@@ -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.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: ["/* NOTE (lts/5): this file carries the clip-fix subset of #4085 only. That PR also\n restyled the form \u2014 card 600->720px, medium field 450->560px, 14px option labels,\n and top-aligned option controls with their nudges \u2014 and those changes render\n differently for deployments that were never clipping, so they stay on `next` and\n are deliberately absent here. Every rule below is a no-op unless the element is\n already overflowing. Expect a conflict when cherry-picking this file again; take\n the wrap/min-width rules and leave the sizing and typography behind. */\n\n.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}\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 /* 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-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 /* without this the label is stuck at its content width and cannot wrap */\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.radio-label i,\n.checkbox-label i {\n color: var(--mj-text-secondary);\n /* keep the icon at its intrinsic size when a long label puts the row under pressure */\n flex: 0 0 auto;\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/* The 350px cap stays as-is on this line: a long selected label degrades to the\n ellipsis above rather than being cut mid-word, so widening the container is a\n layout improvement, not part of the clip fix. See #4085 on `next`. */\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"] }] });
|
|
245
245
|
}
|
|
246
246
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: DynamicFormFieldComponent, decorators: [{
|
|
247
247
|
type: Component,
|
|
@@ -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: ["/* NOTE (lts/5): this file carries the clip-fix subset of #4085 only. That PR also\n restyled the form \u2014 card 600->720px, medium field 450->560px, 14px option labels,\n and top-aligned option controls with their nudges \u2014 and those changes render\n differently for deployments that were never clipping, so they stay on `next` and\n are deliberately absent here. Every rule below is a no-op unless the element is\n already overflowing. Expect a conflict when cherry-picking this file again; take\n the wrap/min-width rules and leave the sizing and typography behind. */\n\n.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}\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 /* 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-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 /* without this the label is stuck at its content width and cannot wrap */\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.radio-label i,\n.checkbox-label i {\n color: var(--mj-text-secondary);\n /* keep the icon at its intrinsic size when a long label puts the row under pressure */\n flex: 0 0 auto;\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/* The 350px cap stays as-is on this line: a long selected label degrades to the\n ellipsis above rather than being cut mid-word, so widening the container is a\n layout improvement, not part of the clip fix. See #4085 on `next`. */\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"] }]
|
|
255
255
|
}], propDecorators: { Question: [{
|
|
256
256
|
type: Input
|
|
257
257
|
}], Control: [{
|
|
@@ -112,11 +112,11 @@ export class DynamicFormResponseComponent {
|
|
|
112
112
|
return field.key;
|
|
113
113
|
}
|
|
114
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:
|
|
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/* NOTE (lts/5): clip-fix subset of #4085 only \u2014 the restyle half of that PR stays on\n `next`. See the matching note in dynamic-form-field.component.css. */\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 /* 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 overflow-wrap: anywhere;\n}\n\n.form-response-pill.single-field i {\n font-size: 11px;\n opacity: 0.95;\n /* keep the icon at its intrinsic size when a long answer puts the pill under pressure */\n flex: 0 0 auto;\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
117
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", 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:
|
|
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/* NOTE (lts/5): clip-fix subset of #4085 only \u2014 the restyle half of that PR stays on\n `next`. See the matching note in dynamic-form-field.component.css. */\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 /* 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 overflow-wrap: anywhere;\n}\n\n.form-response-pill.single-field i {\n font-size: 11px;\n opacity: 0.95;\n /* keep the icon at its intrinsic size when a long answer puts the pill under pressure */\n flex: 0 0 auto;\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: [{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-forms",
|
|
3
|
-
"version": "5.51.
|
|
3
|
+
"version": "5.51.3",
|
|
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",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@angular/compiler": "21.1.3",
|
|
27
27
|
"@angular/compiler-cli": "21.1.3",
|
|
28
|
-
"@memberjunction/ng-test-utils": "5.51.
|
|
28
|
+
"@memberjunction/ng-test-utils": "5.51.3",
|
|
29
29
|
"vitest": "^4.0.18"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"@angular/forms": "21.1.3"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@memberjunction/ai-core-plus": "5.51.
|
|
38
|
-
"@memberjunction/ng-markdown": "5.51.
|
|
39
|
-
"@memberjunction/ng-ui-components": "5.51.
|
|
37
|
+
"@memberjunction/ai-core-plus": "5.51.3",
|
|
38
|
+
"@memberjunction/ng-markdown": "5.51.3",
|
|
39
|
+
"@memberjunction/ng-ui-components": "5.51.3",
|
|
40
40
|
"tslib": "^2.8.1"
|
|
41
41
|
},
|
|
42
42
|
"sideEffects": false,
|