mn-angular-lib 1.0.73 → 1.0.75
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.
|
@@ -5092,11 +5092,11 @@ class MnFormBodyComponent {
|
|
|
5092
5092
|
}
|
|
5093
5093
|
}
|
|
5094
5094
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnFormBodyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5095
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnFormBodyComponent, isStandalone: true, selector: "mn-form-body", inputs: { config: "config", modalRef: "modalRef", hideFooter: "hideFooter", hideCustomBody: "hideCustomBody" }, outputs: { formStatusChange: "formStatusChange" }, viewQueries: [{ propertyName: "inputFields", predicate: MnInputField, descendants: true }, { propertyName: "textareas", predicate: MnTextarea, descendants: true }], ngImport: i0, template: "@if ((config.component || config.template) && !hideCustomBody) {\n <mn-custom-body-host\n [config]=\"asAny(config)\"\n [modalRef]=\"asAny(modalRef)\"\n class=\"mb-6 block\"\n ></mn-custom-body-host>\n}\n\n<form (ngSubmit)=\"submit()\" [formGroup]=\"form\" class=\"flex flex-col gap-6 h-full\">\n <!-- Shared field rendering template (must be inside form for formControlName) -->\n <ng-template #fieldTemplate let-rowField>\n @switch (rowField.field.kind) {\n @case (FieldKind.TEXT) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'text',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n mask: asField(rowField.field).mask,\n autocomplete: asField(rowField.field).autocomplete,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.NUMBER) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'number',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.PASSWORD) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'password',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.SELECT) {\n <div class=\"flex flex-col\">\n <mn-lib-select\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"getSelectProps(rowField.field)\"\n ></mn-lib-select>\n @if (isFieldLoading(asKey(rowField.field.key))) {\n <div class=\"flex items-center gap-1 pl-2 pt-1\">\n <div class=\"w-4 h-4 border-2 border-base-300 border-t-blue-500 rounded-full animate-spin\"></div>\n </div>\n }\n @if (getFieldError(asKey(rowField.field.key))) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.CHECKBOX) {\n <div>\n <mn-lib-checkbox\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n disabled: isFieldDisabled(rowField.field) || isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-checkbox>\n </div>\n }\n\n @case (FieldKind.DATE) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'date',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n startDate: asField(rowField.field).minDate,\n endDate: asField(rowField.field).maxDate,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.DATETIME) {\n <div>\n <mn-lib-datetime\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n mode: asField(rowField.field).mode || 'datetime-local',\n min: asField(rowField.field).min,\n max: asField(rowField.field).max,\n step: asField(rowField.field).step,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-datetime>\n </div>\n }\n\n @case (FieldKind.TEXTAREA) {\n <div>\n <mn-lib-textarea\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n rows: asField(rowField.field).rows || 4,\n resize: 'vertical',\n autocomplete: asField(rowField.field).autocomplete,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-textarea>\n </div>\n }\n\n @case (FieldKind.MULTI_SELECT) {\n <div>\n @if (isFieldLoading(asKey(rowField.field.key))) {\n <div class=\"flex items-center gap-2 py-2 text-sm text-base-content/50\">\n <div class=\"w-4 h-4 border-2 border-base-300 border-t-blue-500 rounded-full animate-spin\"></div>\n {{ labels.loadingOptions }}\n </div>\n }\n @if (!isFieldLoading(asKey(rowField.field.key))) {\n <mn-lib-multi-select\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n options: getFieldOptions(rowField.field),\n searchable: asField(rowField.field).searchable,\n searchPlaceholder: asField(rowField.field).searchPlaceholder,\n maxSelections: asField(rowField.field).maxSelections,\n disabled: isFieldDisabled(rowField.field) || isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-multi-select>\n }\n @if (getFieldError(asKey(rowField.field.key))) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.MULTI_SELECT_TABLE) {\n <ng-container [ngTemplateOutlet]=\"selectTableTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n }\n\n @case (FieldKind.SINGLE_SELECT_TABLE) {\n <ng-container [ngTemplateOutlet]=\"selectTableTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n }\n\n @case (FieldKind.COLOR) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div class=\"flex items-center gap-3\">\n <input\n type=\"color\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"w-10 h-10 rounded-lg border border-base-300 cursor-pointer p-0.5\"\n [value]=\"getColorValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (input)=\"onColorChange(rowField.field, $event)\"\n />\n <span class=\"text-sm text-base-content/60 font-mono\">{{ getColorValue(rowField.field) }}</span>\n </div>\n @if (asField(rowField.field).swatches) {\n <div class=\"flex gap-1.5 mt-1\">\n @for (swatch of asField(rowField.field).swatches; track swatch) {\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"w-6 h-6 rounded-md border border-base-300 cursor-pointer transition-transform hover:scale-110\"\n [style.background-color]=\"swatch\"\n [class.ring-2]=\"getColorValue(rowField.field) === swatch\"\n [class.ring-blue-500]=\"getColorValue(rowField.field) === swatch\"\n (click)=\"setColorFromSwatch(rowField.field, swatch)\"\n [attr.aria-label]=\"'Select color ' + swatch\"\n ></button>\n }\n </div>\n }\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.RATING) {\n <div class=\"flex flex-col gap-1\">\n <span [id]=\"'rating-label-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </span>\n <div [attr.aria-labelledby]=\"'rating-label-' + asKey(rowField.field.key)\" class=\"flex items-center gap-1\"\n role=\"group\">\n @for (star of getRatingRange(rowField.field); track star) {\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"text-2xl cursor-pointer transition-colors focus:outline-none\"\n [class.text-yellow-400]=\"star <= getRatingValue(rowField.field)\"\n [class.text-base-300]=\"star > getRatingValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (click)=\"setRating(rowField.field, star)\"\n >\n ★\n </button>\n }\n <span class=\"text-sm text-base-content/50 ml-2\">{{ getRatingValue(rowField.field) }} / {{ asField(rowField.field).max || 5 }}</span>\n </div>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.SLIDER) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div class=\"flex items-center gap-3\">\n <input\n type=\"range\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"flex-1 h-2 bg-base-200 rounded-lg appearance-none cursor-pointer accent-blue-500\"\n [attr.min]=\"asField(rowField.field).min ?? 0\"\n [attr.max]=\"asField(rowField.field).max ?? 100\"\n [attr.step]=\"asField(rowField.field).step ?? 1\"\n [value]=\"getSliderValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (input)=\"onSliderChange(rowField.field, $event)\"\n />\n @if (asField(rowField.field).showValue !== false) {\n <span class=\"text-sm text-base-content/60 min-w-[3rem] text-right\">\n {{ getSliderValue(rowField.field) }}{{ asField(rowField.field).unit || '' }}\n </span>\n }\n </div>\n <div class=\"flex justify-between text-xs text-base-content/40 px-1\">\n <span>{{ asField(rowField.field).min ?? 0 }}</span>\n <span>{{ asField(rowField.field).max ?? 100 }}</span>\n </div>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.FILE) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div\n class=\"relative border-2 border-dashed border-base-300 rounded-xl p-6 text-center transition-colors hover:border-blue-400 hover:bg-blue-50/30\"\n [class.border-red-300]=\"form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched\"\n [class.opacity-50]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n >\n <input\n type=\"file\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"absolute inset-0 w-full h-full opacity-0 cursor-pointer\"\n [attr.accept]=\"asField(rowField.field).accept || null\"\n [attr.multiple]=\"asField(rowField.field).multiple || null\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (change)=\"onFileChange(rowField.field, $event)\"\n />\n <div class=\"flex flex-col items-center gap-2\">\n <svg class=\"w-8 h-8 text-base-content/40\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5\" />\n </svg>\n <p class=\"text-sm text-base-content/50\">{{ labels.fileUploadPrompt }}</p>\n @if (asField(rowField.field).accept) {\n <p class=\"text-xs text-base-content/40\">{{ labels.accepted }} {{ asField(rowField.field).accept }}</p>\n }\n @if (asField(rowField.field).maxSize) {\n <p class=\"text-xs text-base-content/40\">{{ labels.maxSize }} {{ formatFileSize(asField(rowField.field).maxSize) }}</p>\n }\n </div>\n </div>\n <!-- Selected files list -->\n @if (getSelectedFiles(asKey(rowField.field.key)).length > 0) {\n <div class=\"flex flex-col gap-1 mt-2\">\n @for (file of getSelectedFiles(asKey(rowField.field.key)); track file.name; let i = $index) {\n <div class=\"flex items-center justify-between px-3 py-1.5 bg-base-200 rounded-lg text-sm\">\n <span class=\"text-base-content truncate\">{{ file.name }} ({{ formatFileSize(file.size) }})</span>\n <button mnButton [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\" type=\"button\" class=\"text-base-content/40 hover:text-red-500 ml-2 cursor-pointer\" (click)=\"removeFile(asKey(rowField.field.key), i)\">×</button>\n </div>\n }\n </div>\n }\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFileError(rowField.field) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.CUSTOM) {\n <div>\n <ng-container\n mnCustomFieldHost\n [component]=\"asField(rowField.field).component\"\n [inputs]=\"asField(rowField.field).inputs\"\n [formControlName]=\"asKey(rowField.field.key)\"\n ></ng-container>\n </div>\n }\n\n @default {\n <div></div>\n }\n }\n\n <!-- Show cross-field error below any field that has one -->\n @if (getFieldError(asKey(rowField.field.key)) && rowField.field.kind !== FieldKind.SELECT && rowField.field.kind !== FieldKind.MULTI_SELECT) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </ng-template>\n\n <!-- Shared template for MULTI_SELECT_TABLE and SINGLE_SELECT_TABLE -->\n <ng-template #selectTableTemplate let-rowField>\n <div class=\"flex flex-col gap-1\">\n @if (asField(rowField.field).label) {\n <span class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </span>\n }\n <mn-table\n [dataSource]=\"tableDataSources[asKey(rowField.field.key)]\"\n [attr.aria-label]=\"asField(rowField.field).label || null\"\n (selectionChange)=\"onTableSelectionChange(rowField.field, $event)\"\n ></mn-table>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n </ng-template>\n\n <!-- Field Groups (sections with headers) -->\n @if (fieldGroups.length > 0) {\n <div class=\"flex flex-col gap-6\">\n @for (group of fieldGroups; track group.title) {\n <div class=\"flex flex-col gap-4\" [style.display]=\"isGroupVisible(group) ? '' : 'none'\">\n <div class=\"border-b border-base-300 pb-2\">\n <h3 class=\"text-base font-semibold text-base-content\">{{ group.title }}</h3>\n @if (group.description) {\n <p class=\"text-sm text-base-content/50 mt-0.5\">{{ group.description }}</p>\n }\n </div>\n @for (row of group.rows; track $index) {\n <div class=\"grid gap-4\" [style.grid-template-columns]=\"getGridColumns(row)\">\n @for (rowField of row.fields; track rowField.field.key) {\n <div class=\"flex flex-col gap-2\" [style.grid-column]=\"getGridSpan(rowField)\" [style.display]=\"isFieldVisible(rowField.field) ? '' : 'none'\">\n <ng-container [ngTemplateOutlet]=\"fieldTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Standard rows (no groups) -->\n @if (rows.length > 0) {\n <div class=\"flex flex-col gap-4\">\n @for (row of rows; track $index) {\n <div class=\"grid gap-4\" [style.grid-template-columns]=\"getGridColumns(row)\">\n @for (rowField of row.fields; track rowField.field.key) {\n <div class=\"flex flex-col gap-2\" [style.grid-column]=\"getGridSpan(rowField)\" [style.display]=\"isFieldVisible(rowField.field) ? '' : 'none'\">\n <ng-container [ngTemplateOutlet]=\"fieldTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Form-level errors (not tied to a specific field) -->\n @if (formErrors['_form']) {\n <div class=\"text-red-500 text-sm px-2 py-1 bg-red-50 rounded-md\">\n {{ formErrors['_form'] }}\n </div>\n }\n\n @if (!hideFooter) {\n <div class=\"flex gap-3 pt-4 border-t border-base-300 mt-auto\">\n <button\n mnButton\n type=\"button\"\n [data]=\"{ variant: 'outline', color: 'secondary' }\"\n (mousedown)=\"modalRef.dismiss(ModalCloseReason.CANCELLED)\"\n >\n {{ labels.cancel }}\n </button>\n\n <div class=\"flex-1\"></div>\n\n <button\n mnButton\n type=\"submit\"\n [data]=\"{ variant: 'fill', color: 'primary', disabled: form.invalid || isSubmitting }\"\n [disabled]=\"form.invalid || isSubmitting\"\n >\n {{ isSubmitting ? labels.submitting : labels.submit }}\n </button>\n </div>\n }\n</form>\n", styles: [".select-arrow{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='%236b7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right .75rem center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: MnButton, selector: "button[mnButton], a[mnButton]", inputs: ["data"] }, { kind: "component", type: MnInputField, selector: "mn-lib-input-field", inputs: ["props"] }, { kind: "component", type: MnCheckbox, selector: "mn-lib-checkbox", inputs: ["props", "checked"], outputs: ["checkedChange"] }, { kind: "component", type: MnDatetime, selector: "mn-lib-datetime", inputs: ["props"] }, { kind: "component", type: MnMultiSelect, selector: "mn-lib-multi-select", inputs: ["props"] }, { kind: "component", type: MnTextarea, selector: "mn-lib-textarea", inputs: ["props"] }, { kind: "component", type: MnSelect, selector: "mn-lib-select", inputs: ["props"] }, { kind: "directive", type: MnCustomFieldHostDirective, selector: "[mnCustomFieldHost]", inputs: ["component", "inputs"] }, { kind: "component", type: MnTable, selector: "mn-table", inputs: ["dataSource"], outputs: ["sortChange", "selectionChange", "rowClick"] }, { kind: "component", type: MnCustomBodyHostComponent, selector: "mn-custom-body-host", inputs: ["config", "modalRef"] }] });
|
|
5095
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnFormBodyComponent, isStandalone: true, selector: "mn-form-body", inputs: { config: "config", modalRef: "modalRef", hideFooter: "hideFooter", hideCustomBody: "hideCustomBody" }, outputs: { formStatusChange: "formStatusChange" }, viewQueries: [{ propertyName: "inputFields", predicate: MnInputField, descendants: true }, { propertyName: "textareas", predicate: MnTextarea, descendants: true }], ngImport: i0, template: "@if ((config.component || config.template) && !hideCustomBody) {\n <mn-custom-body-host\n [config]=\"asAny(config)\"\n [modalRef]=\"asAny(modalRef)\"\n class=\"mb-6 block\"\n ></mn-custom-body-host>\n}\n\n<form (ngSubmit)=\"submit()\" [formGroup]=\"form\" class=\"flex flex-col gap-6 h-full\">\n <!-- Shared field rendering template (must be inside form for formControlName) -->\n <ng-template #fieldTemplate let-rowField>\n @switch (rowField.field.kind) {\n @case (FieldKind.TEXT) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'text',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n mask: asField(rowField.field).mask,\n autocomplete: asField(rowField.field).autocomplete,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.NUMBER) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'number',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.PASSWORD) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'password',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.SELECT) {\n <div class=\"flex flex-col\">\n <mn-lib-select\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"getSelectProps(rowField.field)\"\n ></mn-lib-select>\n @if (isFieldLoading(asKey(rowField.field.key))) {\n <div class=\"flex items-center gap-1 pl-2 pt-1\">\n <div class=\"w-4 h-4 border-2 border-base-300 border-t-blue-500 rounded-full animate-spin\"></div>\n </div>\n }\n @if (getFieldError(asKey(rowField.field.key))) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.CHECKBOX) {\n <div>\n <mn-lib-checkbox\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n disabled: isFieldDisabled(rowField.field) || isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-checkbox>\n </div>\n }\n\n @case (FieldKind.DATE) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'date',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n startDate: asField(rowField.field).minDate,\n endDate: asField(rowField.field).maxDate,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.DATETIME) {\n <div>\n <mn-lib-datetime\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n mode: asField(rowField.field).mode || 'datetime-local',\n min: asField(rowField.field).min,\n max: asField(rowField.field).max,\n step: asField(rowField.field).step,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-datetime>\n </div>\n }\n\n @case (FieldKind.TEXTAREA) {\n <div>\n <mn-lib-textarea\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n rows: asField(rowField.field).rows || 4,\n resize: 'vertical',\n autocomplete: asField(rowField.field).autocomplete,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-textarea>\n </div>\n }\n\n @case (FieldKind.MULTI_SELECT) {\n <div>\n @if (isFieldLoading(asKey(rowField.field.key))) {\n <div class=\"flex items-center gap-2 py-2 text-sm text-base-content/50\">\n <div class=\"w-4 h-4 border-2 border-base-300 border-t-blue-500 rounded-full animate-spin\"></div>\n {{ labels.loadingOptions }}\n </div>\n }\n @if (!isFieldLoading(asKey(rowField.field.key))) {\n <mn-lib-multi-select\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n options: getFieldOptions(rowField.field),\n searchable: asField(rowField.field).searchable,\n searchPlaceholder: asField(rowField.field).searchPlaceholder,\n maxSelections: asField(rowField.field).maxSelections,\n disabled: isFieldDisabled(rowField.field) || isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-multi-select>\n }\n @if (getFieldError(asKey(rowField.field.key))) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.MULTI_SELECT_TABLE) {\n <ng-container [ngTemplateOutlet]=\"selectTableTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n }\n\n @case (FieldKind.SINGLE_SELECT_TABLE) {\n <ng-container [ngTemplateOutlet]=\"selectTableTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n }\n\n @case (FieldKind.COLOR) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div class=\"flex items-center gap-3\">\n <input\n type=\"color\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"w-10 h-10 rounded-lg border border-base-300 cursor-pointer p-0.5\"\n [value]=\"getColorValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (input)=\"onColorChange(rowField.field, $event)\"\n />\n <span class=\"text-sm text-base-content/60 font-mono\">{{ getColorValue(rowField.field) }}</span>\n </div>\n @if (asField(rowField.field).swatches) {\n <div class=\"flex gap-1.5 mt-1\">\n @for (swatch of asField(rowField.field).swatches; track swatch) {\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"w-6 h-6 rounded-md border border-base-300 cursor-pointer transition-transform hover:scale-110\"\n [style.background-color]=\"swatch\"\n [class.ring-2]=\"getColorValue(rowField.field) === swatch\"\n [class.ring-blue-500]=\"getColorValue(rowField.field) === swatch\"\n (click)=\"setColorFromSwatch(rowField.field, swatch)\"\n [attr.aria-label]=\"'Select color ' + swatch\"\n ></button>\n }\n </div>\n }\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.RATING) {\n <div class=\"flex flex-col gap-1\">\n <span [id]=\"'rating-label-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </span>\n <div [attr.aria-labelledby]=\"'rating-label-' + asKey(rowField.field.key)\" class=\"flex items-center gap-1\"\n role=\"group\">\n @for (star of getRatingRange(rowField.field); track star) {\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"text-2xl cursor-pointer transition-colors focus:outline-none\"\n [class.text-yellow-400]=\"star <= getRatingValue(rowField.field)\"\n [class.text-base-300]=\"star > getRatingValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (click)=\"setRating(rowField.field, star)\"\n >\n ★\n </button>\n }\n <span class=\"text-sm text-base-content/50 ml-2\">{{ getRatingValue(rowField.field) }} / {{ asField(rowField.field).max || 5 }}</span>\n </div>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.SLIDER) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div class=\"flex items-center gap-3\">\n <input\n type=\"range\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"flex-1 h-2 bg-base-200 rounded-lg appearance-none cursor-pointer accent-blue-500\"\n [attr.min]=\"asField(rowField.field).min ?? 0\"\n [attr.max]=\"asField(rowField.field).max ?? 100\"\n [attr.step]=\"asField(rowField.field).step ?? 1\"\n [value]=\"getSliderValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (input)=\"onSliderChange(rowField.field, $event)\"\n />\n @if (asField(rowField.field).showValue !== false) {\n <span class=\"text-sm text-base-content/60 min-w-[3rem] text-right\">\n {{ getSliderValue(rowField.field) }}{{ asField(rowField.field).unit || '' }}\n </span>\n }\n </div>\n <div class=\"flex justify-between text-xs text-base-content/40 px-1\">\n <span>{{ asField(rowField.field).min ?? 0 }}</span>\n <span>{{ asField(rowField.field).max ?? 100 }}</span>\n </div>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.FILE) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div\n class=\"relative border-2 border-dashed border-base-300 rounded-xl p-6 text-center transition-colors hover:border-blue-400 hover:bg-blue-50/30\"\n [class.border-red-300]=\"form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched\"\n [class.opacity-50]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n >\n <input\n type=\"file\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"absolute inset-0 w-full h-full opacity-0 cursor-pointer\"\n [attr.accept]=\"asField(rowField.field).accept || null\"\n [attr.multiple]=\"asField(rowField.field).multiple || null\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (change)=\"onFileChange(rowField.field, $event)\"\n />\n <div class=\"flex flex-col items-center gap-2\">\n <svg class=\"w-8 h-8 text-base-content/40\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5\" />\n </svg>\n <p class=\"text-sm text-base-content/50\">{{ labels.fileUploadPrompt }}</p>\n @if (asField(rowField.field).accept) {\n <p class=\"text-xs text-base-content/40\">{{ labels.accepted }} {{ asField(rowField.field).accept }}</p>\n }\n @if (asField(rowField.field).maxSize) {\n <p class=\"text-xs text-base-content/40\">{{ labels.maxSize }} {{ formatFileSize(asField(rowField.field).maxSize) }}</p>\n }\n </div>\n </div>\n <!-- Selected files list -->\n @if (getSelectedFiles(asKey(rowField.field.key)).length > 0) {\n <div class=\"flex flex-col gap-1 mt-2\">\n @for (file of getSelectedFiles(asKey(rowField.field.key)); track file.name; let i = $index) {\n <div class=\"flex items-center justify-between px-3 py-1.5 bg-base-200 rounded-lg text-sm\">\n <span class=\"text-base-content truncate\">{{ file.name }} ({{ formatFileSize(file.size) }})</span>\n <button mnButton [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\" type=\"button\" class=\"text-base-content/40 hover:text-red-500 ml-2 cursor-pointer\" (click)=\"removeFile(asKey(rowField.field.key), i)\">×</button>\n </div>\n }\n </div>\n }\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFileError(rowField.field) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.CUSTOM) {\n <div>\n <ng-container\n mnCustomFieldHost\n [component]=\"asField(rowField.field).component\"\n [inputs]=\"asField(rowField.field).inputs\"\n [formControlName]=\"asKey(rowField.field.key)\"\n ></ng-container>\n </div>\n }\n\n @default {\n <div></div>\n }\n }\n\n <!-- Show cross-field error below any field that has one -->\n @if (getFieldError(asKey(rowField.field.key)) && rowField.field.kind !== FieldKind.SELECT && rowField.field.kind !== FieldKind.MULTI_SELECT) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </ng-template>\n\n <!-- Shared template for MULTI_SELECT_TABLE and SINGLE_SELECT_TABLE -->\n <ng-template #selectTableTemplate let-rowField>\n <div class=\"flex flex-col gap-1\">\n @if (asField(rowField.field).label) {\n <span class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </span>\n }\n <mn-table\n [dataSource]=\"tableDataSources[asKey(rowField.field.key)]\"\n [attr.aria-label]=\"asField(rowField.field).label || null\"\n (selectionChange)=\"onTableSelectionChange(rowField.field, $event)\"\n ></mn-table>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n </ng-template>\n\n <!-- Field Groups (sections with headers) -->\n @if (fieldGroups.length > 0) {\n <div class=\"flex flex-col gap-6\">\n @for (group of fieldGroups; track group.title) {\n <div class=\"flex flex-col gap-4\" [style.display]=\"isGroupVisible(group) ? '' : 'none'\">\n <div class=\"border-b border-base-300 pb-2\">\n <h3 class=\"text-base font-semibold text-base-content\">{{ group.title }}</h3>\n @if (group.description) {\n <p class=\"text-sm text-base-content/50 mt-0.5\">{{ group.description }}</p>\n }\n </div>\n @for (row of group.rows; track $index) {\n <div class=\"grid gap-4\" [style.grid-template-columns]=\"getGridColumns(row)\">\n @for (rowField of row.fields; track rowField.field.key) {\n <div class=\"flex flex-col gap-2\" [style.grid-column]=\"getGridSpan(rowField)\" [style.display]=\"isFieldVisible(rowField.field) ? '' : 'none'\">\n <ng-container [ngTemplateOutlet]=\"fieldTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Standard rows (no groups) -->\n @if (rows.length > 0) {\n <div class=\"flex flex-col gap-4\">\n @for (row of rows; track $index) {\n <div class=\"grid gap-4\" [style.grid-template-columns]=\"getGridColumns(row)\">\n @for (rowField of row.fields; track rowField.field.key) {\n <div class=\"flex flex-col gap-2\" [style.grid-column]=\"getGridSpan(rowField)\" [style.display]=\"isFieldVisible(rowField.field) ? '' : 'none'\">\n <ng-container [ngTemplateOutlet]=\"fieldTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Form-level errors (not tied to a specific field) -->\n @if (formErrors['_form']) {\n <div class=\"text-red-500 text-sm px-2 py-1 bg-red-50 rounded-md\">\n {{ formErrors['_form'] }}\n </div>\n }\n\n @if (!hideFooter) {\n <div class=\"flex gap-3 pt-4 pb-6 border-t border-base-300 mt-auto sticky bottom-0 bg-base-100 z-10\">\n <button\n mnButton\n type=\"button\"\n [data]=\"{ variant: 'outline', color: 'secondary' }\"\n (mousedown)=\"modalRef.dismiss(ModalCloseReason.CANCELLED)\"\n >\n {{ labels.cancel }}\n </button>\n\n <div class=\"flex-1\"></div>\n\n <button\n mnButton\n type=\"submit\"\n [data]=\"{ variant: 'fill', color: 'primary', disabled: form.invalid || isSubmitting }\"\n [disabled]=\"form.invalid || isSubmitting\"\n >\n {{ isSubmitting ? labels.submitting : labels.submit }}\n </button>\n </div>\n }\n</form>\n", styles: [".select-arrow{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='%236b7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right .75rem center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: MnButton, selector: "button[mnButton], a[mnButton]", inputs: ["data"] }, { kind: "component", type: MnInputField, selector: "mn-lib-input-field", inputs: ["props"] }, { kind: "component", type: MnCheckbox, selector: "mn-lib-checkbox", inputs: ["props", "checked"], outputs: ["checkedChange"] }, { kind: "component", type: MnDatetime, selector: "mn-lib-datetime", inputs: ["props"] }, { kind: "component", type: MnMultiSelect, selector: "mn-lib-multi-select", inputs: ["props"] }, { kind: "component", type: MnTextarea, selector: "mn-lib-textarea", inputs: ["props"] }, { kind: "component", type: MnSelect, selector: "mn-lib-select", inputs: ["props"] }, { kind: "directive", type: MnCustomFieldHostDirective, selector: "[mnCustomFieldHost]", inputs: ["component", "inputs"] }, { kind: "component", type: MnTable, selector: "mn-table", inputs: ["dataSource"], outputs: ["sortChange", "selectionChange", "rowClick"] }, { kind: "component", type: MnCustomBodyHostComponent, selector: "mn-custom-body-host", inputs: ["config", "modalRef"] }] });
|
|
5096
5096
|
}
|
|
5097
5097
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnFormBodyComponent, decorators: [{
|
|
5098
5098
|
type: Component,
|
|
5099
|
-
args: [{ selector: 'mn-form-body', standalone: true, imports: [CommonModule, ReactiveFormsModule, MnButton, MnInputField, MnCheckbox, MnDatetime, MnMultiSelect, MnTextarea, MnSelect, MnCustomFieldHostDirective, MnTable, MnCustomBodyHostComponent], template: "@if ((config.component || config.template) && !hideCustomBody) {\n <mn-custom-body-host\n [config]=\"asAny(config)\"\n [modalRef]=\"asAny(modalRef)\"\n class=\"mb-6 block\"\n ></mn-custom-body-host>\n}\n\n<form (ngSubmit)=\"submit()\" [formGroup]=\"form\" class=\"flex flex-col gap-6 h-full\">\n <!-- Shared field rendering template (must be inside form for formControlName) -->\n <ng-template #fieldTemplate let-rowField>\n @switch (rowField.field.kind) {\n @case (FieldKind.TEXT) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'text',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n mask: asField(rowField.field).mask,\n autocomplete: asField(rowField.field).autocomplete,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.NUMBER) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'number',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.PASSWORD) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'password',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.SELECT) {\n <div class=\"flex flex-col\">\n <mn-lib-select\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"getSelectProps(rowField.field)\"\n ></mn-lib-select>\n @if (isFieldLoading(asKey(rowField.field.key))) {\n <div class=\"flex items-center gap-1 pl-2 pt-1\">\n <div class=\"w-4 h-4 border-2 border-base-300 border-t-blue-500 rounded-full animate-spin\"></div>\n </div>\n }\n @if (getFieldError(asKey(rowField.field.key))) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.CHECKBOX) {\n <div>\n <mn-lib-checkbox\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n disabled: isFieldDisabled(rowField.field) || isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-checkbox>\n </div>\n }\n\n @case (FieldKind.DATE) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'date',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n startDate: asField(rowField.field).minDate,\n endDate: asField(rowField.field).maxDate,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.DATETIME) {\n <div>\n <mn-lib-datetime\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n mode: asField(rowField.field).mode || 'datetime-local',\n min: asField(rowField.field).min,\n max: asField(rowField.field).max,\n step: asField(rowField.field).step,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-datetime>\n </div>\n }\n\n @case (FieldKind.TEXTAREA) {\n <div>\n <mn-lib-textarea\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n rows: asField(rowField.field).rows || 4,\n resize: 'vertical',\n autocomplete: asField(rowField.field).autocomplete,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-textarea>\n </div>\n }\n\n @case (FieldKind.MULTI_SELECT) {\n <div>\n @if (isFieldLoading(asKey(rowField.field.key))) {\n <div class=\"flex items-center gap-2 py-2 text-sm text-base-content/50\">\n <div class=\"w-4 h-4 border-2 border-base-300 border-t-blue-500 rounded-full animate-spin\"></div>\n {{ labels.loadingOptions }}\n </div>\n }\n @if (!isFieldLoading(asKey(rowField.field.key))) {\n <mn-lib-multi-select\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n options: getFieldOptions(rowField.field),\n searchable: asField(rowField.field).searchable,\n searchPlaceholder: asField(rowField.field).searchPlaceholder,\n maxSelections: asField(rowField.field).maxSelections,\n disabled: isFieldDisabled(rowField.field) || isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-multi-select>\n }\n @if (getFieldError(asKey(rowField.field.key))) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.MULTI_SELECT_TABLE) {\n <ng-container [ngTemplateOutlet]=\"selectTableTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n }\n\n @case (FieldKind.SINGLE_SELECT_TABLE) {\n <ng-container [ngTemplateOutlet]=\"selectTableTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n }\n\n @case (FieldKind.COLOR) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div class=\"flex items-center gap-3\">\n <input\n type=\"color\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"w-10 h-10 rounded-lg border border-base-300 cursor-pointer p-0.5\"\n [value]=\"getColorValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (input)=\"onColorChange(rowField.field, $event)\"\n />\n <span class=\"text-sm text-base-content/60 font-mono\">{{ getColorValue(rowField.field) }}</span>\n </div>\n @if (asField(rowField.field).swatches) {\n <div class=\"flex gap-1.5 mt-1\">\n @for (swatch of asField(rowField.field).swatches; track swatch) {\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"w-6 h-6 rounded-md border border-base-300 cursor-pointer transition-transform hover:scale-110\"\n [style.background-color]=\"swatch\"\n [class.ring-2]=\"getColorValue(rowField.field) === swatch\"\n [class.ring-blue-500]=\"getColorValue(rowField.field) === swatch\"\n (click)=\"setColorFromSwatch(rowField.field, swatch)\"\n [attr.aria-label]=\"'Select color ' + swatch\"\n ></button>\n }\n </div>\n }\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.RATING) {\n <div class=\"flex flex-col gap-1\">\n <span [id]=\"'rating-label-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </span>\n <div [attr.aria-labelledby]=\"'rating-label-' + asKey(rowField.field.key)\" class=\"flex items-center gap-1\"\n role=\"group\">\n @for (star of getRatingRange(rowField.field); track star) {\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"text-2xl cursor-pointer transition-colors focus:outline-none\"\n [class.text-yellow-400]=\"star <= getRatingValue(rowField.field)\"\n [class.text-base-300]=\"star > getRatingValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (click)=\"setRating(rowField.field, star)\"\n >\n ★\n </button>\n }\n <span class=\"text-sm text-base-content/50 ml-2\">{{ getRatingValue(rowField.field) }} / {{ asField(rowField.field).max || 5 }}</span>\n </div>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.SLIDER) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div class=\"flex items-center gap-3\">\n <input\n type=\"range\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"flex-1 h-2 bg-base-200 rounded-lg appearance-none cursor-pointer accent-blue-500\"\n [attr.min]=\"asField(rowField.field).min ?? 0\"\n [attr.max]=\"asField(rowField.field).max ?? 100\"\n [attr.step]=\"asField(rowField.field).step ?? 1\"\n [value]=\"getSliderValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (input)=\"onSliderChange(rowField.field, $event)\"\n />\n @if (asField(rowField.field).showValue !== false) {\n <span class=\"text-sm text-base-content/60 min-w-[3rem] text-right\">\n {{ getSliderValue(rowField.field) }}{{ asField(rowField.field).unit || '' }}\n </span>\n }\n </div>\n <div class=\"flex justify-between text-xs text-base-content/40 px-1\">\n <span>{{ asField(rowField.field).min ?? 0 }}</span>\n <span>{{ asField(rowField.field).max ?? 100 }}</span>\n </div>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.FILE) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div\n class=\"relative border-2 border-dashed border-base-300 rounded-xl p-6 text-center transition-colors hover:border-blue-400 hover:bg-blue-50/30\"\n [class.border-red-300]=\"form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched\"\n [class.opacity-50]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n >\n <input\n type=\"file\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"absolute inset-0 w-full h-full opacity-0 cursor-pointer\"\n [attr.accept]=\"asField(rowField.field).accept || null\"\n [attr.multiple]=\"asField(rowField.field).multiple || null\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (change)=\"onFileChange(rowField.field, $event)\"\n />\n <div class=\"flex flex-col items-center gap-2\">\n <svg class=\"w-8 h-8 text-base-content/40\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5\" />\n </svg>\n <p class=\"text-sm text-base-content/50\">{{ labels.fileUploadPrompt }}</p>\n @if (asField(rowField.field).accept) {\n <p class=\"text-xs text-base-content/40\">{{ labels.accepted }} {{ asField(rowField.field).accept }}</p>\n }\n @if (asField(rowField.field).maxSize) {\n <p class=\"text-xs text-base-content/40\">{{ labels.maxSize }} {{ formatFileSize(asField(rowField.field).maxSize) }}</p>\n }\n </div>\n </div>\n <!-- Selected files list -->\n @if (getSelectedFiles(asKey(rowField.field.key)).length > 0) {\n <div class=\"flex flex-col gap-1 mt-2\">\n @for (file of getSelectedFiles(asKey(rowField.field.key)); track file.name; let i = $index) {\n <div class=\"flex items-center justify-between px-3 py-1.5 bg-base-200 rounded-lg text-sm\">\n <span class=\"text-base-content truncate\">{{ file.name }} ({{ formatFileSize(file.size) }})</span>\n <button mnButton [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\" type=\"button\" class=\"text-base-content/40 hover:text-red-500 ml-2 cursor-pointer\" (click)=\"removeFile(asKey(rowField.field.key), i)\">×</button>\n </div>\n }\n </div>\n }\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFileError(rowField.field) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.CUSTOM) {\n <div>\n <ng-container\n mnCustomFieldHost\n [component]=\"asField(rowField.field).component\"\n [inputs]=\"asField(rowField.field).inputs\"\n [formControlName]=\"asKey(rowField.field.key)\"\n ></ng-container>\n </div>\n }\n\n @default {\n <div></div>\n }\n }\n\n <!-- Show cross-field error below any field that has one -->\n @if (getFieldError(asKey(rowField.field.key)) && rowField.field.kind !== FieldKind.SELECT && rowField.field.kind !== FieldKind.MULTI_SELECT) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </ng-template>\n\n <!-- Shared template for MULTI_SELECT_TABLE and SINGLE_SELECT_TABLE -->\n <ng-template #selectTableTemplate let-rowField>\n <div class=\"flex flex-col gap-1\">\n @if (asField(rowField.field).label) {\n <span class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </span>\n }\n <mn-table\n [dataSource]=\"tableDataSources[asKey(rowField.field.key)]\"\n [attr.aria-label]=\"asField(rowField.field).label || null\"\n (selectionChange)=\"onTableSelectionChange(rowField.field, $event)\"\n ></mn-table>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n </ng-template>\n\n <!-- Field Groups (sections with headers) -->\n @if (fieldGroups.length > 0) {\n <div class=\"flex flex-col gap-6\">\n @for (group of fieldGroups; track group.title) {\n <div class=\"flex flex-col gap-4\" [style.display]=\"isGroupVisible(group) ? '' : 'none'\">\n <div class=\"border-b border-base-300 pb-2\">\n <h3 class=\"text-base font-semibold text-base-content\">{{ group.title }}</h3>\n @if (group.description) {\n <p class=\"text-sm text-base-content/50 mt-0.5\">{{ group.description }}</p>\n }\n </div>\n @for (row of group.rows; track $index) {\n <div class=\"grid gap-4\" [style.grid-template-columns]=\"getGridColumns(row)\">\n @for (rowField of row.fields; track rowField.field.key) {\n <div class=\"flex flex-col gap-2\" [style.grid-column]=\"getGridSpan(rowField)\" [style.display]=\"isFieldVisible(rowField.field) ? '' : 'none'\">\n <ng-container [ngTemplateOutlet]=\"fieldTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Standard rows (no groups) -->\n @if (rows.length > 0) {\n <div class=\"flex flex-col gap-4\">\n @for (row of rows; track $index) {\n <div class=\"grid gap-4\" [style.grid-template-columns]=\"getGridColumns(row)\">\n @for (rowField of row.fields; track rowField.field.key) {\n <div class=\"flex flex-col gap-2\" [style.grid-column]=\"getGridSpan(rowField)\" [style.display]=\"isFieldVisible(rowField.field) ? '' : 'none'\">\n <ng-container [ngTemplateOutlet]=\"fieldTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Form-level errors (not tied to a specific field) -->\n @if (formErrors['_form']) {\n <div class=\"text-red-500 text-sm px-2 py-1 bg-red-50 rounded-md\">\n {{ formErrors['_form'] }}\n </div>\n }\n\n @if (!hideFooter) {\n <div class=\"flex gap-3 pt-4 border-t border-base-300 mt-auto\">\n <button\n mnButton\n type=\"button\"\n [data]=\"{ variant: 'outline', color: 'secondary' }\"\n (mousedown)=\"modalRef.dismiss(ModalCloseReason.CANCELLED)\"\n >\n {{ labels.cancel }}\n </button>\n\n <div class=\"flex-1\"></div>\n\n <button\n mnButton\n type=\"submit\"\n [data]=\"{ variant: 'fill', color: 'primary', disabled: form.invalid || isSubmitting }\"\n [disabled]=\"form.invalid || isSubmitting\"\n >\n {{ isSubmitting ? labels.submitting : labels.submit }}\n </button>\n </div>\n }\n</form>\n", styles: [".select-arrow{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='%236b7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right .75rem center}\n"] }]
|
|
5099
|
+
args: [{ selector: 'mn-form-body', standalone: true, imports: [CommonModule, ReactiveFormsModule, MnButton, MnInputField, MnCheckbox, MnDatetime, MnMultiSelect, MnTextarea, MnSelect, MnCustomFieldHostDirective, MnTable, MnCustomBodyHostComponent], template: "@if ((config.component || config.template) && !hideCustomBody) {\n <mn-custom-body-host\n [config]=\"asAny(config)\"\n [modalRef]=\"asAny(modalRef)\"\n class=\"mb-6 block\"\n ></mn-custom-body-host>\n}\n\n<form (ngSubmit)=\"submit()\" [formGroup]=\"form\" class=\"flex flex-col gap-6 h-full\">\n <!-- Shared field rendering template (must be inside form for formControlName) -->\n <ng-template #fieldTemplate let-rowField>\n @switch (rowField.field.kind) {\n @case (FieldKind.TEXT) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'text',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n mask: asField(rowField.field).mask,\n autocomplete: asField(rowField.field).autocomplete,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.NUMBER) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'number',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.PASSWORD) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'password',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.SELECT) {\n <div class=\"flex flex-col\">\n <mn-lib-select\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"getSelectProps(rowField.field)\"\n ></mn-lib-select>\n @if (isFieldLoading(asKey(rowField.field.key))) {\n <div class=\"flex items-center gap-1 pl-2 pt-1\">\n <div class=\"w-4 h-4 border-2 border-base-300 border-t-blue-500 rounded-full animate-spin\"></div>\n </div>\n }\n @if (getFieldError(asKey(rowField.field.key))) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.CHECKBOX) {\n <div>\n <mn-lib-checkbox\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n disabled: isFieldDisabled(rowField.field) || isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-checkbox>\n </div>\n }\n\n @case (FieldKind.DATE) {\n <div>\n <mn-lib-input-field\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n type: 'date',\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n startDate: asField(rowField.field).minDate,\n endDate: asField(rowField.field).maxDate,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-input-field>\n </div>\n }\n\n @case (FieldKind.DATETIME) {\n <div>\n <mn-lib-datetime\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n mode: asField(rowField.field).mode || 'datetime-local',\n min: asField(rowField.field).min,\n max: asField(rowField.field).max,\n step: asField(rowField.field).step,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-datetime>\n </div>\n }\n\n @case (FieldKind.TEXTAREA) {\n <div>\n <mn-lib-textarea\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n placeholder: asField(rowField.field).placeholder,\n rows: asField(rowField.field).rows || 4,\n resize: 'vertical',\n autocomplete: asField(rowField.field).autocomplete,\n readonly: isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-textarea>\n </div>\n }\n\n @case (FieldKind.MULTI_SELECT) {\n <div>\n @if (isFieldLoading(asKey(rowField.field.key))) {\n <div class=\"flex items-center gap-2 py-2 text-sm text-base-content/50\">\n <div class=\"w-4 h-4 border-2 border-base-300 border-t-blue-500 rounded-full animate-spin\"></div>\n {{ labels.loadingOptions }}\n </div>\n }\n @if (!isFieldLoading(asKey(rowField.field.key))) {\n <mn-lib-multi-select\n [formControlName]=\"asKey(rowField.field.key)\"\n [props]=\"asAny({\n id: asKey(rowField.field.key),\n label: asField(rowField.field).label,\n options: getFieldOptions(rowField.field),\n searchable: asField(rowField.field).searchable,\n searchPlaceholder: asField(rowField.field).searchPlaceholder,\n maxSelections: asField(rowField.field).maxSelections,\n disabled: isFieldDisabled(rowField.field) || isFieldReadOnly(rowField.field)\n })\"\n ></mn-lib-multi-select>\n }\n @if (getFieldError(asKey(rowField.field.key))) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.MULTI_SELECT_TABLE) {\n <ng-container [ngTemplateOutlet]=\"selectTableTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n }\n\n @case (FieldKind.SINGLE_SELECT_TABLE) {\n <ng-container [ngTemplateOutlet]=\"selectTableTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n }\n\n @case (FieldKind.COLOR) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div class=\"flex items-center gap-3\">\n <input\n type=\"color\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"w-10 h-10 rounded-lg border border-base-300 cursor-pointer p-0.5\"\n [value]=\"getColorValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (input)=\"onColorChange(rowField.field, $event)\"\n />\n <span class=\"text-sm text-base-content/60 font-mono\">{{ getColorValue(rowField.field) }}</span>\n </div>\n @if (asField(rowField.field).swatches) {\n <div class=\"flex gap-1.5 mt-1\">\n @for (swatch of asField(rowField.field).swatches; track swatch) {\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"w-6 h-6 rounded-md border border-base-300 cursor-pointer transition-transform hover:scale-110\"\n [style.background-color]=\"swatch\"\n [class.ring-2]=\"getColorValue(rowField.field) === swatch\"\n [class.ring-blue-500]=\"getColorValue(rowField.field) === swatch\"\n (click)=\"setColorFromSwatch(rowField.field, swatch)\"\n [attr.aria-label]=\"'Select color ' + swatch\"\n ></button>\n }\n </div>\n }\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.RATING) {\n <div class=\"flex flex-col gap-1\">\n <span [id]=\"'rating-label-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </span>\n <div [attr.aria-labelledby]=\"'rating-label-' + asKey(rowField.field.key)\" class=\"flex items-center gap-1\"\n role=\"group\">\n @for (star of getRatingRange(rowField.field); track star) {\n <button\n mnButton\n [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\"\n type=\"button\"\n class=\"text-2xl cursor-pointer transition-colors focus:outline-none\"\n [class.text-yellow-400]=\"star <= getRatingValue(rowField.field)\"\n [class.text-base-300]=\"star > getRatingValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (click)=\"setRating(rowField.field, star)\"\n >\n ★\n </button>\n }\n <span class=\"text-sm text-base-content/50 ml-2\">{{ getRatingValue(rowField.field) }} / {{ asField(rowField.field).max || 5 }}</span>\n </div>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.SLIDER) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div class=\"flex items-center gap-3\">\n <input\n type=\"range\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"flex-1 h-2 bg-base-200 rounded-lg appearance-none cursor-pointer accent-blue-500\"\n [attr.min]=\"asField(rowField.field).min ?? 0\"\n [attr.max]=\"asField(rowField.field).max ?? 100\"\n [attr.step]=\"asField(rowField.field).step ?? 1\"\n [value]=\"getSliderValue(rowField.field)\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (input)=\"onSliderChange(rowField.field, $event)\"\n />\n @if (asField(rowField.field).showValue !== false) {\n <span class=\"text-sm text-base-content/60 min-w-[3rem] text-right\">\n {{ getSliderValue(rowField.field) }}{{ asField(rowField.field).unit || '' }}\n </span>\n }\n </div>\n <div class=\"flex justify-between text-xs text-base-content/40 px-1\">\n <span>{{ asField(rowField.field).min ?? 0 }}</span>\n <span>{{ asField(rowField.field).max ?? 100 }}</span>\n </div>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.FILE) {\n <div class=\"flex flex-col gap-1\">\n <label [for]=\"'field-' + asKey(rowField.field.key)\"\n class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n <div\n class=\"relative border-2 border-dashed border-base-300 rounded-xl p-6 text-center transition-colors hover:border-blue-400 hover:bg-blue-50/30\"\n [class.border-red-300]=\"form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched\"\n [class.opacity-50]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n >\n <input\n type=\"file\"\n [id]=\"'field-' + asKey(rowField.field.key)\"\n class=\"absolute inset-0 w-full h-full opacity-0 cursor-pointer\"\n [attr.accept]=\"asField(rowField.field).accept || null\"\n [attr.multiple]=\"asField(rowField.field).multiple || null\"\n [disabled]=\"isFieldReadOnly(rowField.field) || isFieldDisabled(rowField.field)\"\n (change)=\"onFileChange(rowField.field, $event)\"\n />\n <div class=\"flex flex-col items-center gap-2\">\n <svg class=\"w-8 h-8 text-base-content/40\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5\" />\n </svg>\n <p class=\"text-sm text-base-content/50\">{{ labels.fileUploadPrompt }}</p>\n @if (asField(rowField.field).accept) {\n <p class=\"text-xs text-base-content/40\">{{ labels.accepted }} {{ asField(rowField.field).accept }}</p>\n }\n @if (asField(rowField.field).maxSize) {\n <p class=\"text-xs text-base-content/40\">{{ labels.maxSize }} {{ formatFileSize(asField(rowField.field).maxSize) }}</p>\n }\n </div>\n </div>\n <!-- Selected files list -->\n @if (getSelectedFiles(asKey(rowField.field.key)).length > 0) {\n <div class=\"flex flex-col gap-1 mt-2\">\n @for (file of getSelectedFiles(asKey(rowField.field.key)); track file.name; let i = $index) {\n <div class=\"flex items-center justify-between px-3 py-1.5 bg-base-200 rounded-lg text-sm\">\n <span class=\"text-base-content truncate\">{{ file.name }} ({{ formatFileSize(file.size) }})</span>\n <button mnButton [data]=\"{ size: 'sm', variant: 'text', color: 'secondary' }\" type=\"button\" class=\"text-base-content/40 hover:text-red-500 ml-2 cursor-pointer\" (click)=\"removeFile(asKey(rowField.field.key), i)\">×</button>\n </div>\n }\n </div>\n }\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFileError(rowField.field) }}\n </div>\n }\n </div>\n }\n\n @case (FieldKind.CUSTOM) {\n <div>\n <ng-container\n mnCustomFieldHost\n [component]=\"asField(rowField.field).component\"\n [inputs]=\"asField(rowField.field).inputs\"\n [formControlName]=\"asKey(rowField.field.key)\"\n ></ng-container>\n </div>\n }\n\n @default {\n <div></div>\n }\n }\n\n <!-- Show cross-field error below any field that has one -->\n @if (getFieldError(asKey(rowField.field.key)) && rowField.field.kind !== FieldKind.SELECT && rowField.field.kind !== FieldKind.MULTI_SELECT) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ getFieldError(asKey(rowField.field.key)) }}\n </div>\n }\n </ng-template>\n\n <!-- Shared template for MULTI_SELECT_TABLE and SINGLE_SELECT_TABLE -->\n <ng-template #selectTableTemplate let-rowField>\n <div class=\"flex flex-col gap-1\">\n @if (asField(rowField.field).label) {\n <span class=\"pl-2 pb-1 flex flex-row gap-0.5 text-base font-medium text-base-content\">\n {{ asField(rowField.field).label }}\n @if (hasRequiredValidator(rowField.field)) {\n <span class=\"text-red-500\">*</span>\n }\n </span>\n }\n <mn-table\n [dataSource]=\"tableDataSources[asKey(rowField.field.key)]\"\n [attr.aria-label]=\"asField(rowField.field).label || null\"\n (selectionChange)=\"onTableSelectionChange(rowField.field, $event)\"\n ></mn-table>\n @if (form.get(asKey(rowField.field.key))?.invalid && form.get(asKey(rowField.field.key))?.touched) {\n <div class=\"text-red-500 text-xs pl-2 pt-1\">\n {{ labels.fieldRequired }}\n </div>\n }\n </div>\n </ng-template>\n\n <!-- Field Groups (sections with headers) -->\n @if (fieldGroups.length > 0) {\n <div class=\"flex flex-col gap-6\">\n @for (group of fieldGroups; track group.title) {\n <div class=\"flex flex-col gap-4\" [style.display]=\"isGroupVisible(group) ? '' : 'none'\">\n <div class=\"border-b border-base-300 pb-2\">\n <h3 class=\"text-base font-semibold text-base-content\">{{ group.title }}</h3>\n @if (group.description) {\n <p class=\"text-sm text-base-content/50 mt-0.5\">{{ group.description }}</p>\n }\n </div>\n @for (row of group.rows; track $index) {\n <div class=\"grid gap-4\" [style.grid-template-columns]=\"getGridColumns(row)\">\n @for (rowField of row.fields; track rowField.field.key) {\n <div class=\"flex flex-col gap-2\" [style.grid-column]=\"getGridSpan(rowField)\" [style.display]=\"isFieldVisible(rowField.field) ? '' : 'none'\">\n <ng-container [ngTemplateOutlet]=\"fieldTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Standard rows (no groups) -->\n @if (rows.length > 0) {\n <div class=\"flex flex-col gap-4\">\n @for (row of rows; track $index) {\n <div class=\"grid gap-4\" [style.grid-template-columns]=\"getGridColumns(row)\">\n @for (rowField of row.fields; track rowField.field.key) {\n <div class=\"flex flex-col gap-2\" [style.grid-column]=\"getGridSpan(rowField)\" [style.display]=\"isFieldVisible(rowField.field) ? '' : 'none'\">\n <ng-container [ngTemplateOutlet]=\"fieldTemplate\" [ngTemplateOutletContext]=\"{ $implicit: rowField }\"></ng-container>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Form-level errors (not tied to a specific field) -->\n @if (formErrors['_form']) {\n <div class=\"text-red-500 text-sm px-2 py-1 bg-red-50 rounded-md\">\n {{ formErrors['_form'] }}\n </div>\n }\n\n @if (!hideFooter) {\n <div class=\"flex gap-3 pt-4 pb-6 border-t border-base-300 mt-auto sticky bottom-0 bg-base-100 z-10\">\n <button\n mnButton\n type=\"button\"\n [data]=\"{ variant: 'outline', color: 'secondary' }\"\n (mousedown)=\"modalRef.dismiss(ModalCloseReason.CANCELLED)\"\n >\n {{ labels.cancel }}\n </button>\n\n <div class=\"flex-1\"></div>\n\n <button\n mnButton\n type=\"submit\"\n [data]=\"{ variant: 'fill', color: 'primary', disabled: form.invalid || isSubmitting }\"\n [disabled]=\"form.invalid || isSubmitting\"\n >\n {{ isSubmitting ? labels.submitting : labels.submit }}\n </button>\n </div>\n }\n</form>\n", styles: [".select-arrow{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='%236b7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right .75rem center}\n"] }]
|
|
5100
5100
|
}], propDecorators: { config: [{
|
|
5101
5101
|
type: Input
|
|
5102
5102
|
}], modalRef: [{
|
|
@@ -5582,11 +5582,11 @@ class MnWizardBodyComponent {
|
|
|
5582
5582
|
}
|
|
5583
5583
|
}
|
|
5584
5584
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnWizardBodyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5585
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnWizardBodyComponent, isStandalone: true, selector: "mn-wizard-body", inputs: { config: "config", modalRef: "modalRef" }, viewQueries: [{ propertyName: "formBodies", predicate: MnFormBodyComponent, descendants: true }, { propertyName: "stepWrappers", predicate: ["stepWrapper"], descendants: true }], ngImport: i0, template: "<div class=\"flex flex-col
|
|
5585
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnWizardBodyComponent, isStandalone: true, selector: "mn-wizard-body", inputs: { config: "config", modalRef: "modalRef" }, viewQueries: [{ propertyName: "formBodies", predicate: MnFormBodyComponent, descendants: true }, { propertyName: "stepWrappers", predicate: ["stepWrapper"], descendants: true }], ngImport: i0, template: "<div class=\"flex flex-col h-full min-h-0\">\n @if (config.component || config.template) {\n <mn-custom-body-host\n [config]=\"$any(config)\"\n [modalRef]=\"$any(modalRef)\"\n class=\"block flex-none px-6 pt-6\"\n ></mn-custom-body-host>\n }\n\n <!-- Steps header \u2014 fixed (does not scroll with the step body) -->\n <div class=\"flex-none px-6 pt-2 pb-4 border-b border-base-300 bg-base-100\">\n <div class=\"relative flex items-start\">\n <!-- Background line spanning from first circle center to last circle center -->\n <div\n [style.left]=\"'calc(' + 100 / (2 * visibleSteps.length) + '%)'\"\n [style.right]=\"'calc(' + 100 / (2 * visibleSteps.length) + '%)'\"\n class=\"absolute top-3.75 h-1 bg-base-300 z-0\"\n ></div>\n\n <!-- Progress fill line -->\n @if (visibleSteps.length > 1) {\n <div\n [style.left]=\"'calc(' + 100 / (2 * visibleSteps.length) + '%)'\"\n [style.width]=\"'calc(' + (currentProgressIndex / (visibleSteps.length - 1)) * (100 - 100 / visibleSteps.length) + '%)'\"\n class=\"absolute top-3.75 h-1 bg-success z-1 transition-all duration-500\"\n ></div>\n }\n\n @for (step of visibleSteps; track step.id; let i = $index; ) {\n <div\n (keyup.enter)=\"isFreeFlow ? goToStep(step) : null\"\n (click)=\"isFreeFlow ? goToStep(step) : null\"\n [attr.tabindex]=\"isFreeFlow && canNavigateToStep(step) ? 0 : null\"\n [class.cursor-pointer]=\"isFreeFlow && canNavigateToStep(step)\"\n class=\"relative flex flex-col items-center gap-1 z-10 w-0 flex-1\"\n >\n <div\n [ngClass]=\"{\n 'bg-primary text-primary-content': step.id === currentStepId,\n 'bg-success text-success-content': visitedStepIds.includes(step.id) && step.id !== currentStepId,\n 'bg-base-200 text-base-content/50': !visitedStepIds.includes(step.id) && step.id !== currentStepId\n }\"\n class=\"w-8 h-8 rounded-full flex items-center justify-center font-semibold transition-all text-sm\"\n >{{ i + 1 }}\n </div>\n <div\n [ngClass]=\"{\n 'text-base-content font-semibold': step.id === currentStepId,\n 'text-base-content/50': step.id !== currentStepId\n }\"\n class=\"text-xs text-center whitespace-nowrap hidden sm:block\"\n >{{ step.title }}\n </div>\n\n </div>\n }\n </div>\n </div>\n\n <!-- Step body \u2014 the only scrolling region in the wizard -->\n <div class=\"flex-1 min-h-0 overflow-y-auto px-6 py-6\">\n <div [style.min-height]=\"measuredMinHeight ? measuredMinHeight + 'px' : null\" class=\"min-h-48\">\n @for (step of config.steps; track step.id) {\n <div #stepWrapper [style.display]=\"step.id === currentStepId ? 'block' : 'none'\">\n <h3 class=\"text-lg font-semibold text-base-content mb-4\">{{ step.title }}</h3>\n <div class=\"text-base-content/80\">\n <!-- Form step -->\n @if (stepFormConfigs[step.id]) {\n <mn-form-body\n [config]=\"stepFormConfigs[step.id]\"\n [modalRef]=\"$any(modalRef)\"\n [hideFooter]=\"true\"\n [hideCustomBody]=\"true\"\n ></mn-form-body>\n }\n\n <!-- Text body -->\n @if (!stepFormConfigs[step.id] && isTextBody(step)) {\n <div>\n {{ step.body }}\n </div>\n }\n\n <!-- Dynamic content container for component/template bodies -->\n <ng-container #dynamicContainer></ng-container>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Wizard-level errors (from onBeforeComplete) \u2014 fixed, above the footer -->\n @if (wizardErrors && (wizardErrors | keyvalue).length > 0) {\n <div class=\"flex flex-col gap-1 mx-6 mb-2 px-2 py-2 bg-red-50 rounded-md\">\n @for (err of wizardErrors | keyvalue; track err.key) {\n <div class=\"text-red-500 text-sm\">\n {{ err.value }}\n </div>\n }\n </div>\n }\n\n <!-- Footer \u2014 fixed (does not scroll with the step body) -->\n <div class=\"flex-none flex gap-3 px-6 pt-4 pb-6 border-t border-base-300 bg-base-100\">\n @if (!currentStep?.hideBack) {\n <button\n mnButton\n [data]=\"{ variant: 'outline', color: 'secondary' }\"\n (click)=\"back()\"\n >\n {{ currentStep?.backLabel || (canGoBack ? labels.back : labels.close) }}\n </button>\n }\n\n <!-- Custom footer actions -->\n @if (config.footerActions) {\n <mn-footer-actions\n [actions]=\"config.footerActions\"\n (actionClick)=\"handleFooterAction($any($event))\"\n ></mn-footer-actions>\n } @else {\n <div class=\"flex-1\"></div>\n }\n\n @if (!isLastStep) {\n <button\n mnButton\n [data]=\"{ variant: 'fill', color: 'primary', disabled: !isCurrentStepValid }\"\n (click)=\"next()\"\n >\n {{ currentStep?.nextLabel || labels.next }}\n </button>\n }\n\n @if (isLastStep) {\n <button\n mnButton\n [data]=\"{ variant: 'fill', color: 'primary', disabled: !isCurrentStepValid || isCompleting }\"\n [disabled]=\"!isCurrentStepValid || isCompleting\"\n (click)=\"complete()\"\n >\n {{ currentStep?.nextLabel || (isCompleting ? labels.completing : labels.complete) }}\n </button>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: MnButton, selector: "button[mnButton], a[mnButton]", inputs: ["data"] }, { kind: "component", type: MnFormBodyComponent, selector: "mn-form-body", inputs: ["config", "modalRef", "hideFooter", "hideCustomBody"], outputs: ["formStatusChange"] }, { kind: "component", type: MnCustomBodyHostComponent, selector: "mn-custom-body-host", inputs: ["config", "modalRef"] }, { kind: "component", type: MnFooterActionsComponent, selector: "mn-footer-actions", inputs: ["actions"], outputs: ["actionClick"] }, { kind: "pipe", type: i1.KeyValuePipe, name: "keyvalue" }] });
|
|
5586
5586
|
}
|
|
5587
5587
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnWizardBodyComponent, decorators: [{
|
|
5588
5588
|
type: Component,
|
|
5589
|
-
args: [{ selector: 'mn-wizard-body', standalone: true, imports: [CommonModule, ReactiveFormsModule, MnButton, MnFormBodyComponent, MnCustomBodyHostComponent, MnFooterActionsComponent], template: "<div class=\"flex flex-col
|
|
5589
|
+
args: [{ selector: 'mn-wizard-body', standalone: true, imports: [CommonModule, ReactiveFormsModule, MnButton, MnFormBodyComponent, MnCustomBodyHostComponent, MnFooterActionsComponent], template: "<div class=\"flex flex-col h-full min-h-0\">\n @if (config.component || config.template) {\n <mn-custom-body-host\n [config]=\"$any(config)\"\n [modalRef]=\"$any(modalRef)\"\n class=\"block flex-none px-6 pt-6\"\n ></mn-custom-body-host>\n }\n\n <!-- Steps header \u2014 fixed (does not scroll with the step body) -->\n <div class=\"flex-none px-6 pt-2 pb-4 border-b border-base-300 bg-base-100\">\n <div class=\"relative flex items-start\">\n <!-- Background line spanning from first circle center to last circle center -->\n <div\n [style.left]=\"'calc(' + 100 / (2 * visibleSteps.length) + '%)'\"\n [style.right]=\"'calc(' + 100 / (2 * visibleSteps.length) + '%)'\"\n class=\"absolute top-3.75 h-1 bg-base-300 z-0\"\n ></div>\n\n <!-- Progress fill line -->\n @if (visibleSteps.length > 1) {\n <div\n [style.left]=\"'calc(' + 100 / (2 * visibleSteps.length) + '%)'\"\n [style.width]=\"'calc(' + (currentProgressIndex / (visibleSteps.length - 1)) * (100 - 100 / visibleSteps.length) + '%)'\"\n class=\"absolute top-3.75 h-1 bg-success z-1 transition-all duration-500\"\n ></div>\n }\n\n @for (step of visibleSteps; track step.id; let i = $index; ) {\n <div\n (keyup.enter)=\"isFreeFlow ? goToStep(step) : null\"\n (click)=\"isFreeFlow ? goToStep(step) : null\"\n [attr.tabindex]=\"isFreeFlow && canNavigateToStep(step) ? 0 : null\"\n [class.cursor-pointer]=\"isFreeFlow && canNavigateToStep(step)\"\n class=\"relative flex flex-col items-center gap-1 z-10 w-0 flex-1\"\n >\n <div\n [ngClass]=\"{\n 'bg-primary text-primary-content': step.id === currentStepId,\n 'bg-success text-success-content': visitedStepIds.includes(step.id) && step.id !== currentStepId,\n 'bg-base-200 text-base-content/50': !visitedStepIds.includes(step.id) && step.id !== currentStepId\n }\"\n class=\"w-8 h-8 rounded-full flex items-center justify-center font-semibold transition-all text-sm\"\n >{{ i + 1 }}\n </div>\n <div\n [ngClass]=\"{\n 'text-base-content font-semibold': step.id === currentStepId,\n 'text-base-content/50': step.id !== currentStepId\n }\"\n class=\"text-xs text-center whitespace-nowrap hidden sm:block\"\n >{{ step.title }}\n </div>\n\n </div>\n }\n </div>\n </div>\n\n <!-- Step body \u2014 the only scrolling region in the wizard -->\n <div class=\"flex-1 min-h-0 overflow-y-auto px-6 py-6\">\n <div [style.min-height]=\"measuredMinHeight ? measuredMinHeight + 'px' : null\" class=\"min-h-48\">\n @for (step of config.steps; track step.id) {\n <div #stepWrapper [style.display]=\"step.id === currentStepId ? 'block' : 'none'\">\n <h3 class=\"text-lg font-semibold text-base-content mb-4\">{{ step.title }}</h3>\n <div class=\"text-base-content/80\">\n <!-- Form step -->\n @if (stepFormConfigs[step.id]) {\n <mn-form-body\n [config]=\"stepFormConfigs[step.id]\"\n [modalRef]=\"$any(modalRef)\"\n [hideFooter]=\"true\"\n [hideCustomBody]=\"true\"\n ></mn-form-body>\n }\n\n <!-- Text body -->\n @if (!stepFormConfigs[step.id] && isTextBody(step)) {\n <div>\n {{ step.body }}\n </div>\n }\n\n <!-- Dynamic content container for component/template bodies -->\n <ng-container #dynamicContainer></ng-container>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Wizard-level errors (from onBeforeComplete) \u2014 fixed, above the footer -->\n @if (wizardErrors && (wizardErrors | keyvalue).length > 0) {\n <div class=\"flex flex-col gap-1 mx-6 mb-2 px-2 py-2 bg-red-50 rounded-md\">\n @for (err of wizardErrors | keyvalue; track err.key) {\n <div class=\"text-red-500 text-sm\">\n {{ err.value }}\n </div>\n }\n </div>\n }\n\n <!-- Footer \u2014 fixed (does not scroll with the step body) -->\n <div class=\"flex-none flex gap-3 px-6 pt-4 pb-6 border-t border-base-300 bg-base-100\">\n @if (!currentStep?.hideBack) {\n <button\n mnButton\n [data]=\"{ variant: 'outline', color: 'secondary' }\"\n (click)=\"back()\"\n >\n {{ currentStep?.backLabel || (canGoBack ? labels.back : labels.close) }}\n </button>\n }\n\n <!-- Custom footer actions -->\n @if (config.footerActions) {\n <mn-footer-actions\n [actions]=\"config.footerActions\"\n (actionClick)=\"handleFooterAction($any($event))\"\n ></mn-footer-actions>\n } @else {\n <div class=\"flex-1\"></div>\n }\n\n @if (!isLastStep) {\n <button\n mnButton\n [data]=\"{ variant: 'fill', color: 'primary', disabled: !isCurrentStepValid }\"\n (click)=\"next()\"\n >\n {{ currentStep?.nextLabel || labels.next }}\n </button>\n }\n\n @if (isLastStep) {\n <button\n mnButton\n [data]=\"{ variant: 'fill', color: 'primary', disabled: !isCurrentStepValid || isCompleting }\"\n [disabled]=\"!isCurrentStepValid || isCompleting\"\n (click)=\"complete()\"\n >\n {{ currentStep?.nextLabel || (isCompleting ? labels.completing : labels.complete) }}\n </button>\n }\n </div>\n</div>\n" }]
|
|
5590
5590
|
}], propDecorators: { config: [{
|
|
5591
5591
|
type: Input
|
|
5592
5592
|
}], modalRef: [{
|
|
@@ -5690,11 +5690,11 @@ class MnConfirmationBodyComponent {
|
|
|
5690
5690
|
return false;
|
|
5691
5691
|
}
|
|
5692
5692
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnConfirmationBodyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5693
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnConfirmationBodyComponent, isStandalone: true, selector: "mn-confirmation-body", inputs: { config: "config", modalRef: "modalRef" }, viewQueries: [{ propertyName: "formBody", first: true, predicate: MnFormBodyComponent, descendants: true }], ngImport: i0, template: "<div class=\"flex flex-col gap-6\" [ngClass]=\"toneClass\">\n <!-- Custom Content (Component or Template) -->\n @if (config.component || config.template) {\n <mn-custom-body-host\n [config]=\"$any(config)\"\n [modalRef]=\"$any(modalRef)\"\n ></mn-custom-body-host>\n }\n\n <div class=\"flex flex-col items-center text-center gap-6\">\n @if (config.tone === 'warning' || config.tone === 'danger') {\n <div\n class=\"w-12 h-12 rounded-full flex items-center justify-center\"\n [ngClass]=\"{\n 'bg-blue-100 text-blue-500': config.tone !== 'warning' && config.tone !== 'danger',\n 'bg-amber-100 text-amber-500': config.tone === 'warning',\n 'bg-red-100 text-red-500': config.tone === 'danger'\n }\">\n @if (config.tone === 'warning') {\n <svg\n class=\"w-6 h-6\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"\n />\n </svg>\n }\n @if (config.tone === 'danger') {\n <svg\n class=\"w-6 h-6\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\"\n />\n </svg>\n }\n </div>\n }\n\n @if (config.message) {\n <div class=\"text-base text-base-content/80 leading-relaxed max-w-[28rem]\">\n {{ config.message }}\n </div>\n }\n </div>\n\n <!-- Form Fields / Rows -->\n @if (hasFormFields) {\n <mn-form-body\n [config]=\"$any(config)\"\n [modalRef]=\"$any(modalRef)\"\n [hideFooter]=\"true\"\n [hideCustomBody]=\"true\"\n (formStatusChange)=\"onFormStatusChange($event)\"\n ></mn-form-body>\n }\n\n <div class=\"flex gap-3 w-full\">\n <button\n mnButton\n [data]=\"{\n variant: getButtonVariant(cancelStyle),\n color: getButtonColor(cancelStyle)\n }\"\n (click)=\"cancel()\"\n >\n {{ cancelLabel }}\n </button>\n\n <div class=\"flex-1\"></div>\n\n <button\n mnButton\n [data]=\"{\n variant: getButtonVariant(confirmStyle),\n color: getButtonColor(confirmStyle),\n disabled: isConfirmDisabled\n }\"\n [disabled]=\"isConfirmDisabled\"\n (click)=\"confirm()\"\n >\n {{ confirmLabel }}\n </button>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MnButton, selector: "button[mnButton], a[mnButton]", inputs: ["data"] }, { kind: "component", type: MnFormBodyComponent, selector: "mn-form-body", inputs: ["config", "modalRef", "hideFooter", "hideCustomBody"], outputs: ["formStatusChange"] }, { kind: "component", type: MnCustomBodyHostComponent, selector: "mn-custom-body-host", inputs: ["config", "modalRef"] }, { kind: "ngmodule", type: ReactiveFormsModule }] });
|
|
5693
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnConfirmationBodyComponent, isStandalone: true, selector: "mn-confirmation-body", inputs: { config: "config", modalRef: "modalRef" }, viewQueries: [{ propertyName: "formBody", first: true, predicate: MnFormBodyComponent, descendants: true }], ngImport: i0, template: "<div class=\"flex flex-col gap-6\" [ngClass]=\"toneClass\">\n <!-- Custom Content (Component or Template) -->\n @if (config.component || config.template) {\n <mn-custom-body-host\n [config]=\"$any(config)\"\n [modalRef]=\"$any(modalRef)\"\n ></mn-custom-body-host>\n }\n\n <div class=\"flex flex-col items-center text-center gap-6\">\n @if (config.tone === 'warning' || config.tone === 'danger') {\n <div\n class=\"w-12 h-12 rounded-full flex items-center justify-center\"\n [ngClass]=\"{\n 'bg-blue-100 text-blue-500': config.tone !== 'warning' && config.tone !== 'danger',\n 'bg-amber-100 text-amber-500': config.tone === 'warning',\n 'bg-red-100 text-red-500': config.tone === 'danger'\n }\">\n @if (config.tone === 'warning') {\n <svg\n class=\"w-6 h-6\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"\n />\n </svg>\n }\n @if (config.tone === 'danger') {\n <svg\n class=\"w-6 h-6\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\"\n />\n </svg>\n }\n </div>\n }\n\n @if (config.message) {\n <div class=\"text-base text-base-content/80 leading-relaxed max-w-[28rem]\">\n {{ config.message }}\n </div>\n }\n </div>\n\n <!-- Form Fields / Rows -->\n @if (hasFormFields) {\n <mn-form-body\n [config]=\"$any(config)\"\n [modalRef]=\"$any(modalRef)\"\n [hideFooter]=\"true\"\n [hideCustomBody]=\"true\"\n (formStatusChange)=\"onFormStatusChange($event)\"\n ></mn-form-body>\n }\n\n <div class=\"flex gap-3 w-full pb-6 sticky bottom-0 bg-base-100 z-10\">\n <button\n mnButton\n [data]=\"{\n variant: getButtonVariant(cancelStyle),\n color: getButtonColor(cancelStyle)\n }\"\n (click)=\"cancel()\"\n >\n {{ cancelLabel }}\n </button>\n\n <div class=\"flex-1\"></div>\n\n <button\n mnButton\n [data]=\"{\n variant: getButtonVariant(confirmStyle),\n color: getButtonColor(confirmStyle),\n disabled: isConfirmDisabled\n }\"\n [disabled]=\"isConfirmDisabled\"\n (click)=\"confirm()\"\n >\n {{ confirmLabel }}\n </button>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MnButton, selector: "button[mnButton], a[mnButton]", inputs: ["data"] }, { kind: "component", type: MnFormBodyComponent, selector: "mn-form-body", inputs: ["config", "modalRef", "hideFooter", "hideCustomBody"], outputs: ["formStatusChange"] }, { kind: "component", type: MnCustomBodyHostComponent, selector: "mn-custom-body-host", inputs: ["config", "modalRef"] }, { kind: "ngmodule", type: ReactiveFormsModule }] });
|
|
5694
5694
|
}
|
|
5695
5695
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnConfirmationBodyComponent, decorators: [{
|
|
5696
5696
|
type: Component,
|
|
5697
|
-
args: [{ selector: 'mn-confirmation-body', standalone: true, imports: [CommonModule, MnButton, MnFormBodyComponent, MnCustomBodyHostComponent, ReactiveFormsModule], template: "<div class=\"flex flex-col gap-6\" [ngClass]=\"toneClass\">\n <!-- Custom Content (Component or Template) -->\n @if (config.component || config.template) {\n <mn-custom-body-host\n [config]=\"$any(config)\"\n [modalRef]=\"$any(modalRef)\"\n ></mn-custom-body-host>\n }\n\n <div class=\"flex flex-col items-center text-center gap-6\">\n @if (config.tone === 'warning' || config.tone === 'danger') {\n <div\n class=\"w-12 h-12 rounded-full flex items-center justify-center\"\n [ngClass]=\"{\n 'bg-blue-100 text-blue-500': config.tone !== 'warning' && config.tone !== 'danger',\n 'bg-amber-100 text-amber-500': config.tone === 'warning',\n 'bg-red-100 text-red-500': config.tone === 'danger'\n }\">\n @if (config.tone === 'warning') {\n <svg\n class=\"w-6 h-6\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"\n />\n </svg>\n }\n @if (config.tone === 'danger') {\n <svg\n class=\"w-6 h-6\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\"\n />\n </svg>\n }\n </div>\n }\n\n @if (config.message) {\n <div class=\"text-base text-base-content/80 leading-relaxed max-w-[28rem]\">\n {{ config.message }}\n </div>\n }\n </div>\n\n <!-- Form Fields / Rows -->\n @if (hasFormFields) {\n <mn-form-body\n [config]=\"$any(config)\"\n [modalRef]=\"$any(modalRef)\"\n [hideFooter]=\"true\"\n [hideCustomBody]=\"true\"\n (formStatusChange)=\"onFormStatusChange($event)\"\n ></mn-form-body>\n }\n\n <div class=\"flex gap-3 w-full\">\n <button\n mnButton\n [data]=\"{\n variant: getButtonVariant(cancelStyle),\n color: getButtonColor(cancelStyle)\n }\"\n (click)=\"cancel()\"\n >\n {{ cancelLabel }}\n </button>\n\n <div class=\"flex-1\"></div>\n\n <button\n mnButton\n [data]=\"{\n variant: getButtonVariant(confirmStyle),\n color: getButtonColor(confirmStyle),\n disabled: isConfirmDisabled\n }\"\n [disabled]=\"isConfirmDisabled\"\n (click)=\"confirm()\"\n >\n {{ confirmLabel }}\n </button>\n </div>\n</div>\n" }]
|
|
5697
|
+
args: [{ selector: 'mn-confirmation-body', standalone: true, imports: [CommonModule, MnButton, MnFormBodyComponent, MnCustomBodyHostComponent, ReactiveFormsModule], template: "<div class=\"flex flex-col gap-6\" [ngClass]=\"toneClass\">\n <!-- Custom Content (Component or Template) -->\n @if (config.component || config.template) {\n <mn-custom-body-host\n [config]=\"$any(config)\"\n [modalRef]=\"$any(modalRef)\"\n ></mn-custom-body-host>\n }\n\n <div class=\"flex flex-col items-center text-center gap-6\">\n @if (config.tone === 'warning' || config.tone === 'danger') {\n <div\n class=\"w-12 h-12 rounded-full flex items-center justify-center\"\n [ngClass]=\"{\n 'bg-blue-100 text-blue-500': config.tone !== 'warning' && config.tone !== 'danger',\n 'bg-amber-100 text-amber-500': config.tone === 'warning',\n 'bg-red-100 text-red-500': config.tone === 'danger'\n }\">\n @if (config.tone === 'warning') {\n <svg\n class=\"w-6 h-6\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"\n />\n </svg>\n }\n @if (config.tone === 'danger') {\n <svg\n class=\"w-6 h-6\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\"\n />\n </svg>\n }\n </div>\n }\n\n @if (config.message) {\n <div class=\"text-base text-base-content/80 leading-relaxed max-w-[28rem]\">\n {{ config.message }}\n </div>\n }\n </div>\n\n <!-- Form Fields / Rows -->\n @if (hasFormFields) {\n <mn-form-body\n [config]=\"$any(config)\"\n [modalRef]=\"$any(modalRef)\"\n [hideFooter]=\"true\"\n [hideCustomBody]=\"true\"\n (formStatusChange)=\"onFormStatusChange($event)\"\n ></mn-form-body>\n }\n\n <div class=\"flex gap-3 w-full pb-6 sticky bottom-0 bg-base-100 z-10\">\n <button\n mnButton\n [data]=\"{\n variant: getButtonVariant(cancelStyle),\n color: getButtonColor(cancelStyle)\n }\"\n (click)=\"cancel()\"\n >\n {{ cancelLabel }}\n </button>\n\n <div class=\"flex-1\"></div>\n\n <button\n mnButton\n [data]=\"{\n variant: getButtonVariant(confirmStyle),\n color: getButtonColor(confirmStyle),\n disabled: isConfirmDisabled\n }\"\n [disabled]=\"isConfirmDisabled\"\n (click)=\"confirm()\"\n >\n {{ confirmLabel }}\n </button>\n </div>\n</div>\n" }]
|
|
5698
5698
|
}], propDecorators: { config: [{
|
|
5699
5699
|
type: Input
|
|
5700
5700
|
}], modalRef: [{
|
|
@@ -5819,18 +5819,70 @@ class MnModalShellComponent {
|
|
|
5819
5819
|
onCloseButtonClick() {
|
|
5820
5820
|
this.handleClose(ModalCloseReason.DISMISSED);
|
|
5821
5821
|
}
|
|
5822
|
+
static SWIPE_DISMISS_THRESHOLD = 100;
|
|
5823
|
+
// =========================
|
|
5824
|
+
// Mobile bottom-sheet swipe-to-dismiss (via the grabber handle)
|
|
5825
|
+
// =========================
|
|
5826
|
+
/** Current downward drag offset (px) applied to the sheet while swiping. */
|
|
5827
|
+
sheetDragY = 0;
|
|
5828
|
+
/** True while the user is actively dragging the grabber (disables snap transition). */
|
|
5829
|
+
isDraggingSheet = false;
|
|
5830
|
+
dragStartY = 0;
|
|
5831
|
+
/** Whether the sheet can be dismissed at all (drives whether the swipe is armed). */
|
|
5832
|
+
get canClose() {
|
|
5833
|
+
return this.config.closeMode !== CloseMode.DISABLED;
|
|
5834
|
+
}
|
|
5835
|
+
/** Tailwind's `sm` breakpoint — below this the modal renders as a bottom sheet. */
|
|
5836
|
+
static SHEET_MAX_WIDTH = 639.98;
|
|
5837
|
+
onSheetPointerDown(event) {
|
|
5838
|
+
if (!this.isMobileSheet || !this.canClose)
|
|
5839
|
+
return;
|
|
5840
|
+
// Only a bottom sheet (mobile-width viewport) can be swiped away.
|
|
5841
|
+
if (window.innerWidth > MnModalShellComponent.SHEET_MAX_WIDTH)
|
|
5842
|
+
return;
|
|
5843
|
+
// Don't hijack drags that begin on an interactive control (e.g. the close button).
|
|
5844
|
+
if (event.target.closest('button'))
|
|
5845
|
+
return;
|
|
5846
|
+
this.isDraggingSheet = true;
|
|
5847
|
+
this.dragStartY = event.clientY;
|
|
5848
|
+
event.target.setPointerCapture(event.pointerId);
|
|
5849
|
+
}
|
|
5850
|
+
onSheetPointerMove(event) {
|
|
5851
|
+
if (!this.isDraggingSheet)
|
|
5852
|
+
return;
|
|
5853
|
+
// Only track downward movement.
|
|
5854
|
+
this.sheetDragY = Math.max(0, event.clientY - this.dragStartY);
|
|
5855
|
+
}
|
|
5856
|
+
async onSheetPointerUp() {
|
|
5857
|
+
if (!this.isDraggingSheet)
|
|
5858
|
+
return;
|
|
5859
|
+
this.isDraggingSheet = false;
|
|
5860
|
+
if (this.sheetDragY > MnModalShellComponent.SWIPE_DISMISS_THRESHOLD) {
|
|
5861
|
+
const closed = await this.handleClose(ModalCloseReason.DISMISSED);
|
|
5862
|
+
if (!closed) {
|
|
5863
|
+
this.sheetDragY = 0; // guard rejected — spring back
|
|
5864
|
+
}
|
|
5865
|
+
// closed === true: leave it; the closing animation takes over.
|
|
5866
|
+
}
|
|
5867
|
+
else {
|
|
5868
|
+
this.sheetDragY = 0; // not far enough — spring back
|
|
5869
|
+
}
|
|
5870
|
+
}
|
|
5871
|
+
/** Attempts to dismiss the modal. Resolves true if it was actually dismissed,
|
|
5872
|
+
* false if blocked by a DISABLED close mode or a rejected close guard. */
|
|
5822
5873
|
async handleClose(reason) {
|
|
5823
5874
|
if (this.config.closeMode === CloseMode.DISABLED) {
|
|
5824
|
-
return;
|
|
5875
|
+
return false;
|
|
5825
5876
|
}
|
|
5826
5877
|
if (this.config.closeMode === CloseMode.GUARDED) {
|
|
5827
5878
|
if (this.config.closeGuard) {
|
|
5828
5879
|
const allowed = await this.config.closeGuard();
|
|
5829
5880
|
if (!allowed)
|
|
5830
|
-
return;
|
|
5881
|
+
return false;
|
|
5831
5882
|
}
|
|
5832
5883
|
}
|
|
5833
5884
|
this.modalRef.dismiss(reason);
|
|
5885
|
+
return true;
|
|
5834
5886
|
}
|
|
5835
5887
|
get showBackdrop() {
|
|
5836
5888
|
return this.config.backdrop !== BackdropMode.HIDE;
|
|
@@ -5944,7 +5996,7 @@ class MnModalShellComponent {
|
|
|
5944
5996
|
}
|
|
5945
5997
|
}
|
|
5946
5998
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnModalShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5947
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnModalShellComponent, isStandalone: true, selector: "mn-modal-shell", inputs: { config: "config", modalRef: "modalRef" }, host: { listeners: { "document:keydown.escape": "onEscapeKey($event)" }, properties: { "class": "this.hostClasses" } }, ngImport: i0, template: "@if (showBackdrop) {\n <!-- The backdrop is a visual overlay \u2014 keyboard dismiss is handled at component level via Escape key -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"modal-backdrop absolute inset-0 bg-black/50 animate-[fadeIn_0.2s_ease-in-out]\" (click)=\"onBackdropClick()\"></div>\n}\n\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n<div\n class=\"modal-container relative bg-base-100 rounded-lg shadow-xl max-h-[90vh] overflow-hidden flex flex-col\"\n [ngClass]=\"containerSizeClass\"\n [style.height]=\"containerHeightStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"config.title ? 'mn-modal-title' : null\"\n [attr.aria-describedby]=\"config.description ? 'mn-modal-description' : null\"\n tabindex=\"-1\"\n (click)=\"$event.stopPropagation()\"\n>\n @if (isMobileSheet) {\n <!-- Drag
|
|
5999
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnModalShellComponent, isStandalone: true, selector: "mn-modal-shell", inputs: { config: "config", modalRef: "modalRef" }, host: { listeners: { "document:keydown.escape": "onEscapeKey($event)" }, properties: { "class": "this.hostClasses" } }, ngImport: i0, template: "@if (showBackdrop) {\n <!-- The backdrop is a visual overlay \u2014 keyboard dismiss is handled at component level via Escape key -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"modal-backdrop absolute inset-0 bg-black/50 animate-[fadeIn_0.2s_ease-in-out]\" (click)=\"onBackdropClick()\"></div>\n}\n\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n<div\n class=\"modal-container relative bg-base-100 rounded-lg shadow-xl max-h-[90vh] overflow-hidden flex flex-col\"\n [class.sheet-dragging]=\"isDraggingSheet\"\n [ngClass]=\"containerSizeClass\"\n [style.height]=\"containerHeightStyle\"\n [style.transform]=\"sheetDragY ? 'translateY(' + sheetDragY + 'px)' : null\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"config.title ? 'mn-modal-title' : null\"\n [attr.aria-describedby]=\"config.description ? 'mn-modal-description' : null\"\n tabindex=\"-1\"\n (click)=\"$event.stopPropagation()\"\n>\n @if (isMobileSheet) {\n <!-- Drag handle for swipe-to-dismiss, visible only on mobile bottom sheets -->\n <div\n (pointercancel)=\"onSheetPointerUp()\"\n (pointerdown)=\"onSheetPointerDown($event)\"\n (pointermove)=\"onSheetPointerMove($event)\"\n (pointerup)=\"onSheetPointerUp()\"\n class=\"sm:hidden flex justify-center pt-2 pb-1 touch-none cursor-grab\"\n >\n <div class=\"h-1.5 w-10 rounded-full bg-base-300\"></div>\n </div>\n }\n\n <!-- On mobile the whole header doubles as a swipe-to-dismiss surface (drags that\n start on the close button are ignored). The grabber above is the visual cue. -->\n <div [class.border-b]=\"config.kind !== ModalKind.WIZARD\"\n [class.border-base-300]=\"config.kind !== ModalKind.WIZARD\"\n (pointercancel)=\"onSheetPointerUp()\"\n (pointerdown)=\"onSheetPointerDown($event)\"\n (pointermove)=\"onSheetPointerMove($event)\"\n (pointerup)=\"onSheetPointerUp()\"\n [class.cursor-grab]=\"isMobileSheet\"\n [class.sm:cursor-auto]=\"isMobileSheet\"\n [class.sm:touch-auto]=\"isMobileSheet\"\n [class.touch-none]=\"isMobileSheet\"\n class=\"flex items-center justify-between p-6\">\n <div class=\"flex flex-col gap-0.5\">\n @if (config.title) {\n <h2 class=\"m-0 text-xl font-semibold text-base-content\" id=\"mn-modal-title\">{{ config.title }}</h2>\n }\n @if (config.subtitle) {\n <p class=\"m-0 text-sm text-base-content/60 font-normal\">{{ config.subtitle }}</p>\n }\n </div>\n @if (showCloseButton) {\n <button\n class=\"bg-transparent border-none text-2xl cursor-pointer text-base-content/50 p-0 w-8 h-8 flex items-center justify-center rounded transition-colors hover:bg-base-200 hover:text-base-content\"\n (click)=\"onCloseButtonClick()\"\n aria-label=\"Close modal\"\n >\n \u00D7\n </button>\n }\n </div>\n @if (config.description) {\n <p class=\"m-0 px-6 text-sm text-base-content/60 leading-relaxed\" id=\"mn-modal-description\">{{ config.description }}</p>\n }\n\n @if (config.kind === ModalKind.WIZARD) {\n <!-- Wizard manages its own internal scrolling so the steps header and footer\n stay fixed while only the step body scrolls. No padding/scroll here. -->\n <div class=\"flex-1 min-h-0 overflow-hidden\">\n <mn-wizard-body\n [config]=\"asWizard(config)\"\n [modalRef]=\"$any(modalRef)\"\n class=\"block h-full\"\n ></mn-wizard-body>\n </div>\n } @else {\n <div class=\"flex-1 overflow-y-auto px-6 pt-6\">\n @if (config.kind === ModalKind.FORM) {\n <mn-form-body\n [config]=\"asForm(config)\"\n [modalRef]=\"$any(modalRef)\"\n class=\"block h-full\"\n ></mn-form-body>\n }\n\n @if (config.kind === ModalKind.CONFIRMATION) {\n <mn-confirmation-body\n [config]=\"asConfirmation(config)\"\n [modalRef]=\"$any(modalRef)\"\n ></mn-confirmation-body>\n }\n\n @if (config.kind === ModalKind.CUSTOM) {\n <mn-custom-body-host\n [config]=\"asCustom(config)\"\n [modalRef]=\"$any(modalRef)\"\n class=\"block pb-6\"\n ></mn-custom-body-host>\n }\n </div>\n }\n\n <!-- Custom Footer Actions (not for wizard modals, they render their own) -->\n @if (hasCustomFooterActions && config.kind !== ModalKind.WIZARD) {\n <div class=\"flex gap-3 p-6 border-t border-base-300\">\n <mn-footer-actions\n [actions]=\"config.footerActions || []\"\n (actionClick)=\"onFooterAction($event)\"\n ></mn-footer-actions>\n </div>\n }\n</div>\n", styles: [":host{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;transition:transform .3s ease-in-out,filter .3s ease-in-out,opacity .3s ease-in-out}:host(.is-stacked){transform:scale(.96) translateY(-1rem);filter:brightness(.9) blur(1px);pointer-events:none;opacity:.8}.modal-container{transition:transform .2s ease}.modal-container.sheet-dragging{transition:none}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideIn{0%{opacity:0;transform:translateY(-1rem)}to{opacity:1;transform:translateY(0)}}@keyframes zoomIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes slideOut{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(1rem)}}@keyframes zoomOut{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@keyframes slideUpIn{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@keyframes slideUpOut{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(100%)}}:host(.anim-slide) .modal-container{animation:slideIn .2s ease-in-out}:host(.anim-fade) .modal-container{animation:fadeIn .2s ease-in-out}:host(.anim-zoom) .modal-container{animation:zoomIn .2s ease-in-out}:host(.closing) .modal-backdrop{animation:fadeOut .15s ease-in-out forwards}:host(.closing).anim-slide .modal-container{animation:slideOut .15s ease-in-out forwards}:host(.closing).anim-fade .modal-container{animation:fadeOut .15s ease-in-out forwards}:host(.closing).anim-zoom .modal-container{animation:zoomOut .15s ease-in-out forwards}@media(max-width:639.98px){:host(.mobile-sheet){align-items:flex-end}:host(.mobile-sheet) .modal-container{width:100%;max-width:100%;max-height:92vh;border-radius:1rem 1rem 0 0;animation:slideUpIn .25s ease-out}:host(.mobile-sheet).anim-slide .modal-container,:host(.mobile-sheet).anim-fade .modal-container,:host(.mobile-sheet).anim-zoom .modal-container{animation:slideUpIn .25s ease-out}:host(.mobile-sheet).closing .modal-container,:host(.mobile-sheet).closing.anim-slide .modal-container,:host(.mobile-sheet).closing.anim-fade .modal-container,:host(.mobile-sheet).closing.anim-zoom .modal-container{animation:slideUpOut .2s ease-in forwards}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MnWizardBodyComponent, selector: "mn-wizard-body", inputs: ["config", "modalRef"] }, { kind: "component", type: MnFormBodyComponent, selector: "mn-form-body", inputs: ["config", "modalRef", "hideFooter", "hideCustomBody"], outputs: ["formStatusChange"] }, { kind: "component", type: MnConfirmationBodyComponent, selector: "mn-confirmation-body", inputs: ["config", "modalRef"] }, { kind: "component", type: MnCustomBodyHostComponent, selector: "mn-custom-body-host", inputs: ["config", "modalRef"] }, { kind: "component", type: MnFooterActionsComponent, selector: "mn-footer-actions", inputs: ["actions"], outputs: ["actionClick"] }] });
|
|
5948
6000
|
}
|
|
5949
6001
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnModalShellComponent, decorators: [{
|
|
5950
6002
|
type: Component,
|
|
@@ -5955,7 +6007,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
5955
6007
|
MnConfirmationBodyComponent,
|
|
5956
6008
|
MnCustomBodyHostComponent,
|
|
5957
6009
|
MnFooterActionsComponent,
|
|
5958
|
-
], template: "@if (showBackdrop) {\n <!-- The backdrop is a visual overlay \u2014 keyboard dismiss is handled at component level via Escape key -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"modal-backdrop absolute inset-0 bg-black/50 animate-[fadeIn_0.2s_ease-in-out]\" (click)=\"onBackdropClick()\"></div>\n}\n\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n<div\n class=\"modal-container relative bg-base-100 rounded-lg shadow-xl max-h-[90vh] overflow-hidden flex flex-col\"\n [ngClass]=\"containerSizeClass\"\n [style.height]=\"containerHeightStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"config.title ? 'mn-modal-title' : null\"\n [attr.aria-describedby]=\"config.description ? 'mn-modal-description' : null\"\n tabindex=\"-1\"\n (click)=\"$event.stopPropagation()\"\n>\n @if (isMobileSheet) {\n <!-- Drag
|
|
6010
|
+
], template: "@if (showBackdrop) {\n <!-- The backdrop is a visual overlay \u2014 keyboard dismiss is handled at component level via Escape key -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"modal-backdrop absolute inset-0 bg-black/50 animate-[fadeIn_0.2s_ease-in-out]\" (click)=\"onBackdropClick()\"></div>\n}\n\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n<div\n class=\"modal-container relative bg-base-100 rounded-lg shadow-xl max-h-[90vh] overflow-hidden flex flex-col\"\n [class.sheet-dragging]=\"isDraggingSheet\"\n [ngClass]=\"containerSizeClass\"\n [style.height]=\"containerHeightStyle\"\n [style.transform]=\"sheetDragY ? 'translateY(' + sheetDragY + 'px)' : null\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"config.title ? 'mn-modal-title' : null\"\n [attr.aria-describedby]=\"config.description ? 'mn-modal-description' : null\"\n tabindex=\"-1\"\n (click)=\"$event.stopPropagation()\"\n>\n @if (isMobileSheet) {\n <!-- Drag handle for swipe-to-dismiss, visible only on mobile bottom sheets -->\n <div\n (pointercancel)=\"onSheetPointerUp()\"\n (pointerdown)=\"onSheetPointerDown($event)\"\n (pointermove)=\"onSheetPointerMove($event)\"\n (pointerup)=\"onSheetPointerUp()\"\n class=\"sm:hidden flex justify-center pt-2 pb-1 touch-none cursor-grab\"\n >\n <div class=\"h-1.5 w-10 rounded-full bg-base-300\"></div>\n </div>\n }\n\n <!-- On mobile the whole header doubles as a swipe-to-dismiss surface (drags that\n start on the close button are ignored). The grabber above is the visual cue. -->\n <div [class.border-b]=\"config.kind !== ModalKind.WIZARD\"\n [class.border-base-300]=\"config.kind !== ModalKind.WIZARD\"\n (pointercancel)=\"onSheetPointerUp()\"\n (pointerdown)=\"onSheetPointerDown($event)\"\n (pointermove)=\"onSheetPointerMove($event)\"\n (pointerup)=\"onSheetPointerUp()\"\n [class.cursor-grab]=\"isMobileSheet\"\n [class.sm:cursor-auto]=\"isMobileSheet\"\n [class.sm:touch-auto]=\"isMobileSheet\"\n [class.touch-none]=\"isMobileSheet\"\n class=\"flex items-center justify-between p-6\">\n <div class=\"flex flex-col gap-0.5\">\n @if (config.title) {\n <h2 class=\"m-0 text-xl font-semibold text-base-content\" id=\"mn-modal-title\">{{ config.title }}</h2>\n }\n @if (config.subtitle) {\n <p class=\"m-0 text-sm text-base-content/60 font-normal\">{{ config.subtitle }}</p>\n }\n </div>\n @if (showCloseButton) {\n <button\n class=\"bg-transparent border-none text-2xl cursor-pointer text-base-content/50 p-0 w-8 h-8 flex items-center justify-center rounded transition-colors hover:bg-base-200 hover:text-base-content\"\n (click)=\"onCloseButtonClick()\"\n aria-label=\"Close modal\"\n >\n \u00D7\n </button>\n }\n </div>\n @if (config.description) {\n <p class=\"m-0 px-6 text-sm text-base-content/60 leading-relaxed\" id=\"mn-modal-description\">{{ config.description }}</p>\n }\n\n @if (config.kind === ModalKind.WIZARD) {\n <!-- Wizard manages its own internal scrolling so the steps header and footer\n stay fixed while only the step body scrolls. No padding/scroll here. -->\n <div class=\"flex-1 min-h-0 overflow-hidden\">\n <mn-wizard-body\n [config]=\"asWizard(config)\"\n [modalRef]=\"$any(modalRef)\"\n class=\"block h-full\"\n ></mn-wizard-body>\n </div>\n } @else {\n <div class=\"flex-1 overflow-y-auto px-6 pt-6\">\n @if (config.kind === ModalKind.FORM) {\n <mn-form-body\n [config]=\"asForm(config)\"\n [modalRef]=\"$any(modalRef)\"\n class=\"block h-full\"\n ></mn-form-body>\n }\n\n @if (config.kind === ModalKind.CONFIRMATION) {\n <mn-confirmation-body\n [config]=\"asConfirmation(config)\"\n [modalRef]=\"$any(modalRef)\"\n ></mn-confirmation-body>\n }\n\n @if (config.kind === ModalKind.CUSTOM) {\n <mn-custom-body-host\n [config]=\"asCustom(config)\"\n [modalRef]=\"$any(modalRef)\"\n class=\"block pb-6\"\n ></mn-custom-body-host>\n }\n </div>\n }\n\n <!-- Custom Footer Actions (not for wizard modals, they render their own) -->\n @if (hasCustomFooterActions && config.kind !== ModalKind.WIZARD) {\n <div class=\"flex gap-3 p-6 border-t border-base-300\">\n <mn-footer-actions\n [actions]=\"config.footerActions || []\"\n (actionClick)=\"onFooterAction($event)\"\n ></mn-footer-actions>\n </div>\n }\n</div>\n", styles: [":host{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;transition:transform .3s ease-in-out,filter .3s ease-in-out,opacity .3s ease-in-out}:host(.is-stacked){transform:scale(.96) translateY(-1rem);filter:brightness(.9) blur(1px);pointer-events:none;opacity:.8}.modal-container{transition:transform .2s ease}.modal-container.sheet-dragging{transition:none}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideIn{0%{opacity:0;transform:translateY(-1rem)}to{opacity:1;transform:translateY(0)}}@keyframes zoomIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes slideOut{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(1rem)}}@keyframes zoomOut{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@keyframes slideUpIn{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@keyframes slideUpOut{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(100%)}}:host(.anim-slide) .modal-container{animation:slideIn .2s ease-in-out}:host(.anim-fade) .modal-container{animation:fadeIn .2s ease-in-out}:host(.anim-zoom) .modal-container{animation:zoomIn .2s ease-in-out}:host(.closing) .modal-backdrop{animation:fadeOut .15s ease-in-out forwards}:host(.closing).anim-slide .modal-container{animation:slideOut .15s ease-in-out forwards}:host(.closing).anim-fade .modal-container{animation:fadeOut .15s ease-in-out forwards}:host(.closing).anim-zoom .modal-container{animation:zoomOut .15s ease-in-out forwards}@media(max-width:639.98px){:host(.mobile-sheet){align-items:flex-end}:host(.mobile-sheet) .modal-container{width:100%;max-width:100%;max-height:92vh;border-radius:1rem 1rem 0 0;animation:slideUpIn .25s ease-out}:host(.mobile-sheet).anim-slide .modal-container,:host(.mobile-sheet).anim-fade .modal-container,:host(.mobile-sheet).anim-zoom .modal-container{animation:slideUpIn .25s ease-out}:host(.mobile-sheet).closing .modal-container,:host(.mobile-sheet).closing.anim-slide .modal-container,:host(.mobile-sheet).closing.anim-fade .modal-container,:host(.mobile-sheet).closing.anim-zoom .modal-container{animation:slideUpOut .2s ease-in forwards}}\n"] }]
|
|
5959
6011
|
}], propDecorators: { config: [{
|
|
5960
6012
|
type: Input
|
|
5961
6013
|
}], modalRef: [{
|