@fovestta2/web-angular 1.0.24 → 1.0.25

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.
@@ -1007,15 +1007,45 @@ class FvFileSelectorComponent {
1007
1007
  }
1008
1008
  // Subscribe to value changes
1009
1009
  this.subscription = this.control.valueChanges.subscribe((value) => {
1010
+ this.handleValue(value);
1010
1011
  this.validateValue(value);
1011
1012
  this.valueChange.emit(value);
1012
1013
  });
1013
1014
  // Validate initial value
1014
- if (this.control.value) {
1015
- this.selectedFile = this.control.value;
1016
- }
1015
+ this.handleValue(this.control.value);
1017
1016
  this.validateValue(this.control.value);
1018
1017
  }
1018
+ handleValue(value) {
1019
+ if (!value) {
1020
+ this.selectedFile = null;
1021
+ return;
1022
+ }
1023
+ if (typeof value === 'string') {
1024
+ // Handle API URL/String
1025
+ const name = value.split('/').pop() || 'Document';
1026
+ // Guess type from extension
1027
+ const ext = name.split('.').pop()?.toLowerCase() || '';
1028
+ let type = 'application/octet-stream';
1029
+ if (ext === 'pdf')
1030
+ type = 'application/pdf';
1031
+ if (['png', 'jpg', 'jpeg', 'gif'].includes(ext))
1032
+ type = `image/${ext}`;
1033
+ if (['xls', 'xlsx'].includes(ext))
1034
+ type = 'application/vnd.ms-excel';
1035
+ if (['doc', 'docx'].includes(ext))
1036
+ type = 'application/msword';
1037
+ this.selectedFile = {
1038
+ file: new File([''], name, { type }),
1039
+ name: name,
1040
+ size: 0,
1041
+ type: type
1042
+ };
1043
+ }
1044
+ else {
1045
+ // Assume it's a FileInfo object
1046
+ this.selectedFile = value;
1047
+ }
1048
+ }
1019
1049
  ngOnDestroy() {
1020
1050
  this.subscription?.unsubscribe();
1021
1051
  }
@@ -1180,12 +1210,30 @@ class FvImageSelectorComponent {
1180
1210
  if (!this.control)
1181
1211
  return;
1182
1212
  this.subscription = this.control.valueChanges.subscribe((value) => {
1213
+ this.handleValue(value);
1183
1214
  this.validateValue(value);
1184
1215
  this.valueChange.emit(value);
1185
1216
  });
1186
- if (this.control.value) {
1187
- this.selectedImage = this.control.value;
1188
- this.validateValue(this.control.value);
1217
+ this.handleValue(this.control.value);
1218
+ this.validateValue(this.control.value);
1219
+ }
1220
+ handleValue(value) {
1221
+ if (!value) {
1222
+ this.selectedImage = null;
1223
+ return;
1224
+ }
1225
+ if (typeof value === 'string') {
1226
+ const name = value.split('/').pop() || 'Image';
1227
+ this.selectedImage = {
1228
+ file: new File([''], name, { type: 'image/jpeg' }),
1229
+ url: this.sanitizer.bypassSecurityTrustUrl(value),
1230
+ width: 0,
1231
+ height: 0,
1232
+ size: 0
1233
+ };
1234
+ }
1235
+ else {
1236
+ this.selectedImage = value;
1189
1237
  }
1190
1238
  }
1191
1239
  ngOnDestroy() {
@@ -2736,6 +2784,7 @@ class FvDocumentFieldComponent {
2736
2784
  showPreview = false;
2737
2785
  previewUrl = null;
2738
2786
  subscription;
2787
+ fileInfo = null;
2739
2788
  constructor(sanitizer) {
2740
2789
  this.sanitizer = sanitizer;
2741
2790
  }
@@ -2745,14 +2794,45 @@ class FvDocumentFieldComponent {
2745
2794
  return;
2746
2795
  }
2747
2796
  this.subscription = this.control.valueChanges.subscribe((value) => {
2797
+ this.handleValue(value);
2748
2798
  this.validateValue(value);
2749
2799
  this.valueChange.emit(value);
2750
2800
  });
2751
2801
  // Initial validation
2802
+ this.handleValue(this.control.value);
2752
2803
  if (this.control.value) {
2753
2804
  this.validateValue(this.control.value);
2754
2805
  }
2755
2806
  }
2807
+ handleValue(value) {
2808
+ if (!value) {
2809
+ this.fileInfo = null;
2810
+ return;
2811
+ }
2812
+ if (typeof value === 'string') {
2813
+ const name = value.split('/').pop() || 'Document';
2814
+ // Guess type from extension
2815
+ const ext = name.split('.').pop()?.toLowerCase() || '';
2816
+ let type = 'application/octet-stream';
2817
+ if (ext === 'pdf')
2818
+ type = 'application/pdf';
2819
+ if (['png', 'jpg', 'jpeg', 'gif'].includes(ext))
2820
+ type = `image/${ext}`;
2821
+ if (['xls', 'xlsx'].includes(ext))
2822
+ type = 'application/vnd.ms-excel';
2823
+ if (['doc', 'docx'].includes(ext))
2824
+ type = 'application/msword';
2825
+ this.fileInfo = {
2826
+ file: value, // Store the URL string as file property if strict typing allows, or cast. Interface says file: File | Blob | string. Perfect.
2827
+ name: name,
2828
+ size: 0,
2829
+ type: type
2830
+ };
2831
+ }
2832
+ else {
2833
+ this.fileInfo = value;
2834
+ }
2835
+ }
2756
2836
  ngOnDestroy() {
2757
2837
  this.subscription?.unsubscribe();
2758
2838
  }
@@ -2812,7 +2892,7 @@ class FvDocumentFieldComponent {
2812
2892
  }
2813
2893
  }
2814
2894
  viewFile() {
2815
- const fileInfo = this.control.value;
2895
+ const fileInfo = this.fileInfo;
2816
2896
  if (!fileInfo)
2817
2897
  return;
2818
2898
  const type = fileInfo.type || '';
@@ -2844,7 +2924,7 @@ class FvDocumentFieldComponent {
2844
2924
  this.previewUrl = null;
2845
2925
  }
2846
2926
  downloadFile() {
2847
- const fileInfo = this.control.value;
2927
+ const fileInfo = this.fileInfo;
2848
2928
  if (!fileInfo)
2849
2929
  return;
2850
2930
  let url;
@@ -2867,7 +2947,7 @@ class FvDocumentFieldComponent {
2867
2947
  }
2868
2948
  }
2869
2949
  getFileIcon() {
2870
- const fileInfo = this.control?.value;
2950
+ const fileInfo = this.fileInfo;
2871
2951
  if (!fileInfo)
2872
2952
  return 'description';
2873
2953
  const type = fileInfo.type || '';
@@ -2896,11 +2976,11 @@ class FvDocumentFieldComponent {
2896
2976
  return errorMessages[this.errorMessage] || this.errorMessage;
2897
2977
  }
2898
2978
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvDocumentFieldComponent, deps: [{ token: i1$1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
2899
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvDocumentFieldComponent, isStandalone: true, selector: "fv-document-field", inputs: { label: "label", placeholder: "placeholder", schema: "schema", control: "control", disabled: "disabled", accept: "accept", maxSize: "maxSize" }, outputs: { valueChange: "valueChange" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"fv-document-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-document-field-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept\" (change)=\"onFileSelected($event)\" style=\"display: none\" />\r\n\r\n <div *ngIf=\"!control.value; else fileUploaded\" class=\"fv-upload-btn-wrapper\">\r\n <button type=\"button\" class=\"fv-upload-button\" [disabled]=\"disabled\" (click)=\"openFileDialog()\">\r\n <span class=\"fv-upload-text\">{{ placeholder }}</span>\r\n <span class=\"fv-upload-icon-section\">\r\n <svg viewBox=\"0 0 24 24\" class=\"upload-icon\">\r\n <path d=\"M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z\" fill=\"currentColor\" />\r\n </svg>\r\n </span>\r\n </button>\r\n </div>\r\n\r\n <ng-template #fileUploaded>\r\n <div class=\"fv-preview-row\">\r\n <div class=\"fv-preview-wrapper\" (click)=\"viewFile()\" style=\"cursor: pointer;\">\r\n <div class=\"fv-file-icon-preview\" [ngClass]=\"getFileIcon()\">\r\n <span class=\"material-icons\">{{ getFileIcon() }}</span>\r\n </div>\r\n </div>\r\n <span class=\"fv-filename\" (click)=\"viewFile()\" style=\"cursor: pointer;\">{{ control.value?.name }}</span>\r\n <button type=\"button\" class=\"fv-trash-btn\" (click)=\"removeFile()\" [disabled]=\"disabled\">\r\n <svg viewBox=\"0 0 24 24\" class=\"trash-icon\">\r\n <path d=\"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\" />\r\n </svg>\r\n </button>\r\n </div>\r\n </ng-template>\r\n\r\n <div *ngIf=\"errorMessage && control?.touched\" class=\"fv-document-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>\r\n\r\n<!-- Preview Overlay/Modal for Image -->\r\n<div *ngIf=\"showPreview\" class=\"fv-preview-overlay\" (click)=\"closePreview()\">\r\n <div class=\"fv-preview-content\" (click)=\"$event.stopPropagation()\">\r\n <img [src]=\"previewUrl\" alt=\"Preview\" />\r\n <div class=\"fv-preview-actions\">\r\n <button type=\"button\" class=\"fv-download-btn\" (click)=\"downloadFile()\">Download</button>\r\n <button type=\"button\" class=\"fv-close-btn\" (click)=\"closePreview()\">Close</button>\r\n </div>\r\n </div>\r\n</div>", styles: ["@import\"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap\";.fv-document-field-container{display:flex;flex-direction:column;margin-bottom:16px;width:fit-content;max-width:100%;font-family:Poppins,sans-serif}.fv-document-field-label{font-size:14px;font-weight:600;color:#151d48;margin-bottom:6px;display:block}.required-asterisk{color:#e74c3c;font-weight:700}.fv-upload-button{display:flex;align-items:center;border:1px solid #dcdcdc;border-radius:8px;background:#fff;padding:0;cursor:pointer;overflow:hidden;width:100px;height:34px;transition:all .2s ease}.fv-upload-button:hover:not(:disabled){border-color:#bbb;box-shadow:0 2px 4px #0000000d}.fv-upload-button:disabled{cursor:not-allowed;background-color:#f9f9f9;opacity:.7}.fv-upload-text{flex:1;padding:0 16px;font-size:14px;color:#1f2b41;text-align:left;font-weight:500}.fv-upload-icon-section{background:#f4f5f7;padding:0 10px;border-left:1px solid #dcdcdc;display:flex;align-items:center;justify-content:center;color:#333;height:100%}.upload-icon{width:20px;height:20px}.fv-preview-row{display:flex;align-items:center;gap:8px;padding:8px 0;width:100px;box-sizing:border-box}.fv-preview-wrapper{position:relative;width:40px;height:40px}.fv-file-icon-preview{width:40px;height:40px;border-radius:8px;background:#f4f5f7;display:flex;align-items:center;justify-content:center;color:#3498db;border:1px solid #dcdcdc}.fv-file-icon-preview.picture_as_pdf{color:#e74c3c;background:#fdf2f2}.fv-file-icon-preview.image{color:#3498db;background:#f1f8fe}.fv-file-icon-preview.table_view{color:#27ae60;background:#f1f9f4}.material-icons{font-size:24px}.fv-filename{flex:1;font-size:14px;color:#666;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:300px}.fv-trash-btn{background:none;border:none;cursor:pointer;padding:6px;border-radius:50%;transition:background .2s;display:flex}.fv-trash-btn:hover:not(:disabled){background:#fee}.trash-icon{width:22px;height:22px;fill:red}.fv-document-error-message{margin-top:4px;font-size:12px;color:#e74c3c}.fv-preview-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#000c;display:flex;justify-content:center;align-items:center;z-index:10000}.fv-preview-content{position:relative;background:#fff;padding:24px;border-radius:12px;max-width:90vw;max-height:90vh;display:flex;flex-direction:column;align-items:center}.fv-preview-content img{max-width:100%;max-height:70vh;object-fit:contain;border-radius:4px;margin-bottom:20px}.fv-preview-actions{display:flex;gap:16px}.fv-preview-actions button{padding:10px 24px;border-radius:6px;border:none;cursor:pointer;font-weight:500;transition:background .2s}.fv-download-btn{background:#3498db;color:#fff}.fv-download-btn:hover{background:#2980b9}.fv-close-btn{background:#f0f0f0;color:#333}.fv-close-btn:hover{background:#e0e0e0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }] });
2979
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvDocumentFieldComponent, isStandalone: true, selector: "fv-document-field", inputs: { label: "label", placeholder: "placeholder", schema: "schema", control: "control", disabled: "disabled", accept: "accept", maxSize: "maxSize" }, outputs: { valueChange: "valueChange" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"fv-document-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-document-field-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept\" (change)=\"onFileSelected($event)\" style=\"display: none\" />\r\n\r\n <div *ngIf=\"!fileInfo; else fileUploaded\" class=\"fv-upload-btn-wrapper\">\r\n <button type=\"button\" class=\"fv-upload-button\" [disabled]=\"disabled\" (click)=\"openFileDialog()\">\r\n <span class=\"fv-upload-text\">{{ placeholder }}</span>\r\n <span class=\"fv-upload-icon-section\">\r\n <svg viewBox=\"0 0 24 24\" class=\"upload-icon\">\r\n <path d=\"M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z\" fill=\"currentColor\" />\r\n </svg>\r\n </span>\r\n </button>\r\n </div>\r\n\r\n <ng-template #fileUploaded>\r\n <div class=\"fv-preview-row\">\r\n <div class=\"fv-preview-wrapper\" (click)=\"viewFile()\" style=\"cursor: pointer;\">\r\n <div class=\"fv-file-icon-preview\" [ngClass]=\"getFileIcon()\">\r\n <span class=\"material-icons\">{{ getFileIcon() }}</span>\r\n </div>\r\n </div>\r\n <span class=\"fv-filename\" (click)=\"viewFile()\" style=\"cursor: pointer;\">{{ fileInfo?.name }}</span>\r\n <button type=\"button\" class=\"fv-trash-btn\" (click)=\"removeFile()\" [disabled]=\"disabled\">\r\n <svg viewBox=\"0 0 24 24\" class=\"trash-icon\">\r\n <path d=\"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\" />\r\n </svg>\r\n </button>\r\n </div>\r\n </ng-template>\r\n\r\n <div *ngIf=\"errorMessage && control?.touched\" class=\"fv-document-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>\r\n\r\n<!-- Preview Overlay/Modal for Image -->\r\n<div *ngIf=\"showPreview\" class=\"fv-preview-overlay\" (click)=\"closePreview()\">\r\n <div class=\"fv-preview-content\" (click)=\"$event.stopPropagation()\">\r\n <img [src]=\"previewUrl\" alt=\"Preview\" />\r\n <div class=\"fv-preview-actions\">\r\n <button type=\"button\" class=\"fv-download-btn\" (click)=\"downloadFile()\">Download</button>\r\n <button type=\"button\" class=\"fv-close-btn\" (click)=\"closePreview()\">Close</button>\r\n </div>\r\n </div>\r\n</div>", styles: ["@import\"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap\";.fv-document-field-container{display:flex;flex-direction:column;margin-bottom:16px;width:fit-content;max-width:100%;font-family:Poppins,sans-serif}.fv-document-field-label{font-size:14px;font-weight:600;color:#151d48;margin-bottom:6px;display:block}.required-asterisk{color:#e74c3c;font-weight:700}.fv-upload-button{display:flex;align-items:center;border:1px solid #dcdcdc;border-radius:8px;background:#fff;padding:0;cursor:pointer;overflow:hidden;width:100px;height:34px;transition:all .2s ease}.fv-upload-button:hover:not(:disabled){border-color:#bbb;box-shadow:0 2px 4px #0000000d}.fv-upload-button:disabled{cursor:not-allowed;background-color:#f9f9f9;opacity:.7}.fv-upload-text{flex:1;padding:0 16px;font-size:14px;color:#1f2b41;text-align:left;font-weight:500}.fv-upload-icon-section{background:#f4f5f7;padding:0 10px;border-left:1px solid #dcdcdc;display:flex;align-items:center;justify-content:center;color:#333;height:100%}.upload-icon{width:20px;height:20px}.fv-preview-row{display:flex;align-items:center;gap:8px;padding:8px 0;width:100px;box-sizing:border-box}.fv-preview-wrapper{position:relative;width:40px;height:40px}.fv-file-icon-preview{width:40px;height:40px;border-radius:8px;background:#f4f5f7;display:flex;align-items:center;justify-content:center;color:#3498db;border:1px solid #dcdcdc}.fv-file-icon-preview.picture_as_pdf{color:#e74c3c;background:#fdf2f2}.fv-file-icon-preview.image{color:#3498db;background:#f1f8fe}.fv-file-icon-preview.table_view{color:#27ae60;background:#f1f9f4}.material-icons{font-size:24px}.fv-filename{flex:1;font-size:14px;color:#666;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:300px}.fv-trash-btn{background:none;border:none;cursor:pointer;padding:6px;border-radius:50%;transition:background .2s;display:flex}.fv-trash-btn:hover:not(:disabled){background:#fee}.trash-icon{width:22px;height:22px;fill:red}.fv-document-error-message{margin-top:4px;font-size:12px;color:#e74c3c}.fv-preview-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#000c;display:flex;justify-content:center;align-items:center;z-index:10000}.fv-preview-content{position:relative;background:#fff;padding:24px;border-radius:12px;max-width:90vw;max-height:90vh;display:flex;flex-direction:column;align-items:center}.fv-preview-content img{max-width:100%;max-height:70vh;object-fit:contain;border-radius:4px;margin-bottom:20px}.fv-preview-actions{display:flex;gap:16px}.fv-preview-actions button{padding:10px 24px;border-radius:6px;border:none;cursor:pointer;font-weight:500;transition:background .2s}.fv-download-btn{background:#3498db;color:#fff}.fv-download-btn:hover{background:#2980b9}.fv-close-btn{background:#f0f0f0;color:#333}.fv-close-btn:hover{background:#e0e0e0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }] });
2900
2980
  }
2901
2981
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvDocumentFieldComponent, decorators: [{
2902
2982
  type: Component,
2903
- args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-document-field', template: "<div class=\"fv-document-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-document-field-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept\" (change)=\"onFileSelected($event)\" style=\"display: none\" />\r\n\r\n <div *ngIf=\"!control.value; else fileUploaded\" class=\"fv-upload-btn-wrapper\">\r\n <button type=\"button\" class=\"fv-upload-button\" [disabled]=\"disabled\" (click)=\"openFileDialog()\">\r\n <span class=\"fv-upload-text\">{{ placeholder }}</span>\r\n <span class=\"fv-upload-icon-section\">\r\n <svg viewBox=\"0 0 24 24\" class=\"upload-icon\">\r\n <path d=\"M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z\" fill=\"currentColor\" />\r\n </svg>\r\n </span>\r\n </button>\r\n </div>\r\n\r\n <ng-template #fileUploaded>\r\n <div class=\"fv-preview-row\">\r\n <div class=\"fv-preview-wrapper\" (click)=\"viewFile()\" style=\"cursor: pointer;\">\r\n <div class=\"fv-file-icon-preview\" [ngClass]=\"getFileIcon()\">\r\n <span class=\"material-icons\">{{ getFileIcon() }}</span>\r\n </div>\r\n </div>\r\n <span class=\"fv-filename\" (click)=\"viewFile()\" style=\"cursor: pointer;\">{{ control.value?.name }}</span>\r\n <button type=\"button\" class=\"fv-trash-btn\" (click)=\"removeFile()\" [disabled]=\"disabled\">\r\n <svg viewBox=\"0 0 24 24\" class=\"trash-icon\">\r\n <path d=\"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\" />\r\n </svg>\r\n </button>\r\n </div>\r\n </ng-template>\r\n\r\n <div *ngIf=\"errorMessage && control?.touched\" class=\"fv-document-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>\r\n\r\n<!-- Preview Overlay/Modal for Image -->\r\n<div *ngIf=\"showPreview\" class=\"fv-preview-overlay\" (click)=\"closePreview()\">\r\n <div class=\"fv-preview-content\" (click)=\"$event.stopPropagation()\">\r\n <img [src]=\"previewUrl\" alt=\"Preview\" />\r\n <div class=\"fv-preview-actions\">\r\n <button type=\"button\" class=\"fv-download-btn\" (click)=\"downloadFile()\">Download</button>\r\n <button type=\"button\" class=\"fv-close-btn\" (click)=\"closePreview()\">Close</button>\r\n </div>\r\n </div>\r\n</div>", styles: ["@import\"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap\";.fv-document-field-container{display:flex;flex-direction:column;margin-bottom:16px;width:fit-content;max-width:100%;font-family:Poppins,sans-serif}.fv-document-field-label{font-size:14px;font-weight:600;color:#151d48;margin-bottom:6px;display:block}.required-asterisk{color:#e74c3c;font-weight:700}.fv-upload-button{display:flex;align-items:center;border:1px solid #dcdcdc;border-radius:8px;background:#fff;padding:0;cursor:pointer;overflow:hidden;width:100px;height:34px;transition:all .2s ease}.fv-upload-button:hover:not(:disabled){border-color:#bbb;box-shadow:0 2px 4px #0000000d}.fv-upload-button:disabled{cursor:not-allowed;background-color:#f9f9f9;opacity:.7}.fv-upload-text{flex:1;padding:0 16px;font-size:14px;color:#1f2b41;text-align:left;font-weight:500}.fv-upload-icon-section{background:#f4f5f7;padding:0 10px;border-left:1px solid #dcdcdc;display:flex;align-items:center;justify-content:center;color:#333;height:100%}.upload-icon{width:20px;height:20px}.fv-preview-row{display:flex;align-items:center;gap:8px;padding:8px 0;width:100px;box-sizing:border-box}.fv-preview-wrapper{position:relative;width:40px;height:40px}.fv-file-icon-preview{width:40px;height:40px;border-radius:8px;background:#f4f5f7;display:flex;align-items:center;justify-content:center;color:#3498db;border:1px solid #dcdcdc}.fv-file-icon-preview.picture_as_pdf{color:#e74c3c;background:#fdf2f2}.fv-file-icon-preview.image{color:#3498db;background:#f1f8fe}.fv-file-icon-preview.table_view{color:#27ae60;background:#f1f9f4}.material-icons{font-size:24px}.fv-filename{flex:1;font-size:14px;color:#666;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:300px}.fv-trash-btn{background:none;border:none;cursor:pointer;padding:6px;border-radius:50%;transition:background .2s;display:flex}.fv-trash-btn:hover:not(:disabled){background:#fee}.trash-icon{width:22px;height:22px;fill:red}.fv-document-error-message{margin-top:4px;font-size:12px;color:#e74c3c}.fv-preview-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#000c;display:flex;justify-content:center;align-items:center;z-index:10000}.fv-preview-content{position:relative;background:#fff;padding:24px;border-radius:12px;max-width:90vw;max-height:90vh;display:flex;flex-direction:column;align-items:center}.fv-preview-content img{max-width:100%;max-height:70vh;object-fit:contain;border-radius:4px;margin-bottom:20px}.fv-preview-actions{display:flex;gap:16px}.fv-preview-actions button{padding:10px 24px;border-radius:6px;border:none;cursor:pointer;font-weight:500;transition:background .2s}.fv-download-btn{background:#3498db;color:#fff}.fv-download-btn:hover{background:#2980b9}.fv-close-btn{background:#f0f0f0;color:#333}.fv-close-btn:hover{background:#e0e0e0}\n"] }]
2983
+ args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-document-field', template: "<div class=\"fv-document-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-document-field-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept\" (change)=\"onFileSelected($event)\" style=\"display: none\" />\r\n\r\n <div *ngIf=\"!fileInfo; else fileUploaded\" class=\"fv-upload-btn-wrapper\">\r\n <button type=\"button\" class=\"fv-upload-button\" [disabled]=\"disabled\" (click)=\"openFileDialog()\">\r\n <span class=\"fv-upload-text\">{{ placeholder }}</span>\r\n <span class=\"fv-upload-icon-section\">\r\n <svg viewBox=\"0 0 24 24\" class=\"upload-icon\">\r\n <path d=\"M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z\" fill=\"currentColor\" />\r\n </svg>\r\n </span>\r\n </button>\r\n </div>\r\n\r\n <ng-template #fileUploaded>\r\n <div class=\"fv-preview-row\">\r\n <div class=\"fv-preview-wrapper\" (click)=\"viewFile()\" style=\"cursor: pointer;\">\r\n <div class=\"fv-file-icon-preview\" [ngClass]=\"getFileIcon()\">\r\n <span class=\"material-icons\">{{ getFileIcon() }}</span>\r\n </div>\r\n </div>\r\n <span class=\"fv-filename\" (click)=\"viewFile()\" style=\"cursor: pointer;\">{{ fileInfo?.name }}</span>\r\n <button type=\"button\" class=\"fv-trash-btn\" (click)=\"removeFile()\" [disabled]=\"disabled\">\r\n <svg viewBox=\"0 0 24 24\" class=\"trash-icon\">\r\n <path d=\"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\" />\r\n </svg>\r\n </button>\r\n </div>\r\n </ng-template>\r\n\r\n <div *ngIf=\"errorMessage && control?.touched\" class=\"fv-document-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>\r\n\r\n<!-- Preview Overlay/Modal for Image -->\r\n<div *ngIf=\"showPreview\" class=\"fv-preview-overlay\" (click)=\"closePreview()\">\r\n <div class=\"fv-preview-content\" (click)=\"$event.stopPropagation()\">\r\n <img [src]=\"previewUrl\" alt=\"Preview\" />\r\n <div class=\"fv-preview-actions\">\r\n <button type=\"button\" class=\"fv-download-btn\" (click)=\"downloadFile()\">Download</button>\r\n <button type=\"button\" class=\"fv-close-btn\" (click)=\"closePreview()\">Close</button>\r\n </div>\r\n </div>\r\n</div>", styles: ["@import\"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap\";.fv-document-field-container{display:flex;flex-direction:column;margin-bottom:16px;width:fit-content;max-width:100%;font-family:Poppins,sans-serif}.fv-document-field-label{font-size:14px;font-weight:600;color:#151d48;margin-bottom:6px;display:block}.required-asterisk{color:#e74c3c;font-weight:700}.fv-upload-button{display:flex;align-items:center;border:1px solid #dcdcdc;border-radius:8px;background:#fff;padding:0;cursor:pointer;overflow:hidden;width:100px;height:34px;transition:all .2s ease}.fv-upload-button:hover:not(:disabled){border-color:#bbb;box-shadow:0 2px 4px #0000000d}.fv-upload-button:disabled{cursor:not-allowed;background-color:#f9f9f9;opacity:.7}.fv-upload-text{flex:1;padding:0 16px;font-size:14px;color:#1f2b41;text-align:left;font-weight:500}.fv-upload-icon-section{background:#f4f5f7;padding:0 10px;border-left:1px solid #dcdcdc;display:flex;align-items:center;justify-content:center;color:#333;height:100%}.upload-icon{width:20px;height:20px}.fv-preview-row{display:flex;align-items:center;gap:8px;padding:8px 0;width:100px;box-sizing:border-box}.fv-preview-wrapper{position:relative;width:40px;height:40px}.fv-file-icon-preview{width:40px;height:40px;border-radius:8px;background:#f4f5f7;display:flex;align-items:center;justify-content:center;color:#3498db;border:1px solid #dcdcdc}.fv-file-icon-preview.picture_as_pdf{color:#e74c3c;background:#fdf2f2}.fv-file-icon-preview.image{color:#3498db;background:#f1f8fe}.fv-file-icon-preview.table_view{color:#27ae60;background:#f1f9f4}.material-icons{font-size:24px}.fv-filename{flex:1;font-size:14px;color:#666;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:300px}.fv-trash-btn{background:none;border:none;cursor:pointer;padding:6px;border-radius:50%;transition:background .2s;display:flex}.fv-trash-btn:hover:not(:disabled){background:#fee}.trash-icon{width:22px;height:22px;fill:red}.fv-document-error-message{margin-top:4px;font-size:12px;color:#e74c3c}.fv-preview-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#000c;display:flex;justify-content:center;align-items:center;z-index:10000}.fv-preview-content{position:relative;background:#fff;padding:24px;border-radius:12px;max-width:90vw;max-height:90vh;display:flex;flex-direction:column;align-items:center}.fv-preview-content img{max-width:100%;max-height:70vh;object-fit:contain;border-radius:4px;margin-bottom:20px}.fv-preview-actions{display:flex;gap:16px}.fv-preview-actions button{padding:10px 24px;border-radius:6px;border:none;cursor:pointer;font-weight:500;transition:background .2s}.fv-download-btn{background:#3498db;color:#fff}.fv-download-btn:hover{background:#2980b9}.fv-close-btn{background:#f0f0f0;color:#333}.fv-close-btn:hover{background:#e0e0e0}\n"] }]
2904
2984
  }], ctorParameters: () => [{ type: i1$1.DomSanitizer }], propDecorators: { label: [{
2905
2985
  type: Input
2906
2986
  }], placeholder: [{