cloud-ide-element 1.0.51 → 1.0.53

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.
@@ -2570,18 +2570,30 @@ class CideEleFileManagerService {
2570
2570
  this.http = http;
2571
2571
  }
2572
2572
  /**
2573
- * Upload a file with base64 encoding
2573
+ * Upload a file with base64 encoding and progress tracking
2574
2574
  * @param file The file to upload
2575
2575
  * @param additionalData Additional data to include in the payload
2576
+ * @param progressCallback Optional callback for progress updates
2576
2577
  * @returns Observable with the upload response
2577
2578
  */
2578
- uploadFile(file, additionalData) {
2579
- // Convert file to base64
2579
+ uploadFile(file, additionalData, progressCallback) {
2580
+ // Convert file to base64 with progress tracking
2580
2581
  return new Observable(observer => {
2581
2582
  const reader = new FileReader();
2583
+ // Track file reading progress
2584
+ reader.onprogress = (event) => {
2585
+ if (event.lengthComputable && progressCallback) {
2586
+ const progress = (event.loaded / event.total) * 50; // File reading is 50% of total progress
2587
+ progressCallback(progress);
2588
+ }
2589
+ };
2582
2590
  reader.onload = () => {
2583
2591
  const base64String = reader.result;
2584
2592
  const base64Data = base64String.split(',')[1]; // Remove data:image/jpeg;base64, prefix
2593
+ // Update progress to 50% (file reading complete)
2594
+ if (progressCallback) {
2595
+ progressCallback(50);
2596
+ }
2585
2597
  const payload = {
2586
2598
  cyfm_name: file.name,
2587
2599
  cyfm_alt_text: additionalData?.altText || file.name,
@@ -2598,10 +2610,24 @@ class CideEleFileManagerService {
2598
2610
  fileData: base64Data, // Base64 file data
2599
2611
  ...additionalData
2600
2612
  };
2601
- // Make the HTTP request
2602
- this.http.post(`${this.baseUrl}/upload`, payload)
2603
- .subscribe({
2604
- next: (response) => observer.next(response),
2613
+ // Make the HTTP request with progress tracking
2614
+ this.http.post(`${this.baseUrl}`, payload, {
2615
+ reportProgress: true,
2616
+ observe: 'events'
2617
+ }).subscribe({
2618
+ next: (event) => {
2619
+ if (event.type === 1 && progressCallback) { // HttpEventType.UploadProgress
2620
+ const uploadProgress = (event.loaded / event.total) * 50; // Upload is remaining 50%
2621
+ const totalProgress = 50 + uploadProgress; // Total progress (50% file reading + upload progress)
2622
+ progressCallback(totalProgress);
2623
+ }
2624
+ else if (event.type === 4) { // HttpEventType.Response
2625
+ if (progressCallback) {
2626
+ progressCallback(100);
2627
+ }
2628
+ observer.next(event.body);
2629
+ }
2630
+ },
2605
2631
  error: (error) => observer.error(error)
2606
2632
  });
2607
2633
  };
@@ -2849,10 +2875,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
2849
2875
  class CideEleFileInputComponent {
2850
2876
  fileManagerService = inject(CideEleFileManagerService);
2851
2877
  notificationService = inject(NotificationService);
2878
+ elementService = inject(CideElementsService);
2852
2879
  label = 'Choose file';
2853
2880
  accept = '';
2854
2881
  multiple = false;
2855
2882
  disabled = false;
2883
+ required = false;
2856
2884
  helperText = '';
2857
2885
  errorText = '';
2858
2886
  showPreview = false;
@@ -2867,6 +2895,7 @@ class CideEleFileInputComponent {
2867
2895
  id = Math.random().toString(36).substring(2, 10);
2868
2896
  isUploading = false;
2869
2897
  uploadProgress = 0;
2898
+ uploadStatus = 'idle';
2870
2899
  uploadNotificationId = null;
2871
2900
  fileChange = new EventEmitter();
2872
2901
  uploadSuccess = new EventEmitter();
@@ -2879,7 +2908,9 @@ class CideEleFileInputComponent {
2879
2908
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
2880
2909
  onChange = (files) => { };
2881
2910
  onTouched = () => { };
2911
+ onValidatorChange = () => { };
2882
2912
  writeValue(files) {
2913
+ console.log('📝 [FileInput] writeValue called with:', files ? Array.from(files).map(f => f.name) : 'null');
2883
2914
  this.files = files;
2884
2915
  this.fileNames = files ? Array.from(files).map(f => f.name) : [];
2885
2916
  this.generatePreviews();
@@ -2890,70 +2921,125 @@ class CideEleFileInputComponent {
2890
2921
  registerOnTouched(fn) {
2891
2922
  this.onTouched = fn;
2892
2923
  }
2924
+ registerOnValidatorChange(fn) {
2925
+ this.onValidatorChange = fn;
2926
+ }
2893
2927
  setDisabledState(isDisabled) {
2894
2928
  this.disabled = isDisabled;
2895
2929
  }
2896
2930
  onFileSelected(event) {
2931
+ console.log('🔍 [FileInput] onFileSelected called');
2897
2932
  const input = event.target;
2898
2933
  this.files = input.files;
2899
2934
  this.fileNames = this.files ? Array.from(this.files).map(f => f.name) : [];
2935
+ console.log('📁 [FileInput] Files selected:', this.fileNames);
2900
2936
  this.generatePreviews();
2937
+ // Reset upload status when new file is selected
2938
+ this.uploadStatus = 'idle';
2939
+ console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus);
2901
2940
  this.onChange(this.files);
2902
2941
  this.fileChange.emit(this.files);
2903
2942
  this.onTouched();
2943
+ this.onValidatorChange();
2904
2944
  // Auto upload if enabled
2905
2945
  if (this.autoUpload && this.files && this.files.length > 0) {
2946
+ console.log('🚀 [FileInput] Auto upload enabled, starting upload for:', this.files[0].name);
2906
2947
  this.uploadFile(this.files[0]);
2907
2948
  }
2949
+ else {
2950
+ console.log('⏸️ [FileInput] Auto upload disabled or no files');
2951
+ }
2908
2952
  }
2909
2953
  clearFiles() {
2954
+ console.log('🗑️ [FileInput] clearFiles called');
2910
2955
  this.files = null;
2911
2956
  this.fileNames = [];
2912
2957
  this.clearPreviews();
2958
+ this.uploadStatus = 'idle';
2959
+ console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus);
2913
2960
  this.onChange(null);
2914
2961
  this.fileChange.emit(null);
2962
+ this.onValidatorChange();
2915
2963
  }
2916
2964
  uploadFile(file) {
2965
+ console.log('📤 [FileInput] uploadFile called for:', file.name, 'Size:', file.size, 'bytes');
2966
+ // Set upload status to 'start' before starting upload
2967
+ this.uploadStatus = 'start';
2968
+ console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus);
2969
+ // Trigger validation update to show upload in progress error
2970
+ this.onValidatorChange();
2971
+ console.log('✅ [FileInput] Validation triggered - should show upload in progress error');
2917
2972
  this.isUploading = true;
2918
2973
  this.uploadProgress = 0;
2919
2974
  this.uploadProgressChange.emit(0);
2920
- // Make form control invalid during upload
2975
+ console.log('📊 [FileInput] Upload progress initialized to 0%');
2976
+ // Make form control invalid during upload - this prevents form submission
2921
2977
  this.onChange(null);
2922
- // Configure file manager service with base URL
2923
- // this.fileManagerService.setBaseUrl(this.baseUrl);
2978
+ console.log('🚫 [FileInput] Form control value set to null to prevent submission');
2924
2979
  // Show initial progress notification
2925
2980
  this.uploadNotificationId = this.notificationService.showProgress('Starting file upload...', 0);
2926
- // Simulate progress for base64 conversion
2927
- this.simulateProgress();
2928
- this.fileManagerService.uploadFile(file, this.uploadData).subscribe({
2981
+ console.log('🔔 [FileInput] Progress notification started with ID:', this.uploadNotificationId);
2982
+ this.fileManagerService.uploadFile(file, this.uploadData, (progress) => {
2983
+ // Real progress callback from file manager service
2984
+ this.uploadProgress = progress;
2985
+ this.uploadProgressChange.emit(progress);
2986
+ console.log('📈 [FileInput] Upload progress:', Math.round(progress) + '%');
2987
+ // Set upload status to 'uploading' when progress starts
2988
+ if (this.uploadStatus === 'start') {
2989
+ this.uploadStatus = 'uploading';
2990
+ console.log('🔄 [FileInput] Upload status changed to:', this.uploadStatus);
2991
+ this.onValidatorChange();
2992
+ console.log('✅ [FileInput] Validation triggered after status change to uploading');
2993
+ }
2994
+ // Update progress notification
2995
+ if (this.uploadNotificationId) {
2996
+ this.notificationService.updateProgress(this.uploadNotificationId, progress, `Uploading file... ${Math.round(progress)}%`);
2997
+ }
2998
+ }).subscribe({
2929
2999
  next: (response) => {
3000
+ console.log('🎉 [FileInput] Upload SUCCESS - Response received:', response);
3001
+ // Set upload status to 'success'
3002
+ this.uploadStatus = 'success';
3003
+ console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus);
3004
+ // Complete the progress
2930
3005
  this.uploadProgress = 100;
2931
3006
  this.uploadProgressChange.emit(100);
3007
+ console.log('📊 [FileInput] Upload progress completed: 100%');
2932
3008
  // Update progress notification to complete
2933
3009
  if (this.uploadNotificationId) {
2934
3010
  this.notificationService.remove(this.uploadNotificationId);
3011
+ console.log('🔔 [FileInput] Progress notification removed');
2935
3012
  }
2936
3013
  this.notificationService.success('File upload completed!');
2937
3014
  this.uploadNotificationId = null;
2938
3015
  // Extract ID from response - the file manager returns the _id of the created record
2939
3016
  const uploadedId = response?._id || response?.data?._id || response?.id || response?.fileId;
2940
3017
  if (uploadedId) {
2941
- console.log('✅ File uploaded successfully with ID:', uploadedId);
3018
+ console.log('✅ [FileInput] File uploaded successfully with ID:', uploadedId);
2942
3019
  this.uploadSuccess.emit(uploadedId);
2943
3020
  // Update the form control value with the uploaded file ID
2944
3021
  this.onChange(uploadedId);
3022
+ console.log('📝 [FileInput] Form control value updated with uploaded file ID');
2945
3023
  }
2946
3024
  else {
2947
- console.error('❌ Upload successful but no ID returned:', response);
3025
+ console.error('❌ [FileInput] Upload successful but no ID returned:', response);
2948
3026
  this.uploadError.emit('Upload successful but no ID returned');
2949
3027
  }
2950
3028
  this.isUploading = false;
3029
+ console.log('🔄 [FileInput] isUploading set to false');
3030
+ // Trigger validation update to clear upload in progress error
3031
+ this.onValidatorChange();
3032
+ console.log('✅ [FileInput] Validation triggered - should clear upload in progress error');
2951
3033
  },
2952
3034
  error: (error) => {
2953
- console.error(' Upload failed:', error);
3035
+ console.error('💥 [FileInput] Upload FAILED:', error);
3036
+ // Set upload status to 'error' and remove upload validation error
3037
+ this.uploadStatus = 'error';
3038
+ console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus);
2954
3039
  // Remove progress notification and show error
2955
3040
  if (this.uploadNotificationId) {
2956
3041
  this.notificationService.remove(this.uploadNotificationId);
3042
+ console.log('🔔 [FileInput] Progress notification removed due to error');
2957
3043
  }
2958
3044
  this.notificationService.error(`Upload failed: ${error.message || error.error?.message || 'Upload failed'}`);
2959
3045
  this.uploadNotificationId = null;
@@ -2961,24 +3047,13 @@ class CideEleFileInputComponent {
2961
3047
  this.isUploading = false;
2962
3048
  this.uploadProgress = 0;
2963
3049
  this.uploadProgressChange.emit(0);
3050
+ console.log('🔄 [FileInput] Upload state reset - isUploading: false, progress: 0%');
3051
+ // Trigger validation update to clear upload in progress error
3052
+ this.onValidatorChange();
3053
+ console.log('✅ [FileInput] Validation triggered - should clear upload in progress error');
2964
3054
  }
2965
3055
  });
2966
3056
  }
2967
- simulateProgress() {
2968
- const interval = setInterval(() => {
2969
- if (this.uploadProgress < 90) {
2970
- this.uploadProgress += Math.random() * 8; // Smaller increments for smoother progress
2971
- this.uploadProgressChange.emit(this.uploadProgress);
2972
- // Update progress notification
2973
- if (this.uploadNotificationId) {
2974
- this.notificationService.updateProgress(this.uploadNotificationId, this.uploadProgress, `Uploading file... ${Math.round(this.uploadProgress)}%`);
2975
- }
2976
- }
2977
- else {
2978
- clearInterval(interval);
2979
- }
2980
- }, 300); // More frequent updates for smoother progress bar
2981
- }
2982
3057
  generatePreviews() {
2983
3058
  // Clear existing previews
2984
3059
  this.clearPreviews();
@@ -3053,14 +3128,96 @@ class CideEleFileInputComponent {
3053
3128
  hasImages() {
3054
3129
  return this.previewUrls.length > 0;
3055
3130
  }
3131
+ isRequired() {
3132
+ return this.required;
3133
+ }
3134
+ getCurrentState() {
3135
+ return {
3136
+ id: this.id,
3137
+ label: this.label,
3138
+ required: this.required,
3139
+ disabled: this.disabled,
3140
+ accept: this.accept,
3141
+ multiple: this.multiple,
3142
+ showPreview: this.showPreview,
3143
+ autoUpload: this.autoUpload,
3144
+ uploadStatus: this.uploadStatus,
3145
+ isUploading: this.isUploading,
3146
+ uploadProgress: this.uploadProgress,
3147
+ files: this.files ? Array.from(this.files).map(f => ({ name: f.name, size: f.size, type: f.type })) : null,
3148
+ fileNames: this.fileNames,
3149
+ previewUrls: this.previewUrls.length,
3150
+ helperText: this.helperText,
3151
+ errorText: this.errorText,
3152
+ placeholderText: this.placeholderText,
3153
+ placeholderIcon: this.placeholderIcon,
3154
+ previewWidth: this.previewWidth,
3155
+ previewHeight: this.previewHeight,
3156
+ previewBoxMode: this.previewBoxMode,
3157
+ showFileName: this.showFileName,
3158
+ uploadData: this.uploadData
3159
+ };
3160
+ }
3161
+ async getControlData() {
3162
+ console.log('🔍 [FileInput] getControlData called');
3163
+ const cide_element_data = await this.elementService?.getElementData({ sype_key: this.id });
3164
+ if (cide_element_data) {
3165
+ console.log('📋 [FileInput] Element data loaded:', cide_element_data);
3166
+ // Update basic properties that exist in ICoreSype
3167
+ this.label = cide_element_data?.sype_label;
3168
+ this.required = (cide_element_data?.sype_required || false);
3169
+ this.placeholderText = (cide_element_data?.sype_placeholder || 'Click to select image');
3170
+ // Update file-input specific properties from data object if available
3171
+ const data = cide_element_data?.data;
3172
+ if (data) {
3173
+ this.accept = data.accept || '';
3174
+ this.multiple = data.multiple || false;
3175
+ this.showPreview = data.showPreview || false;
3176
+ this.autoUpload = data.autoUpload || false;
3177
+ this.placeholderIcon = data.placeholderIcon || '📷';
3178
+ this.previewWidth = data.previewWidth || '200px';
3179
+ this.previewHeight = data.previewHeight || '200px';
3180
+ this.previewBoxMode = data.previewBoxMode || false;
3181
+ this.showFileName = data.showFileName || true;
3182
+ this.uploadData = data.uploadData || {};
3183
+ }
3184
+ console.log('✅ [FileInput] Control data updated from element service');
3185
+ // Trigger validation update
3186
+ this.onValidatorChange();
3187
+ }
3188
+ else {
3189
+ console.log('⚠️ [FileInput] No element data found for key:', this.id);
3190
+ }
3191
+ }
3192
+ // Validator implementation
3193
+ validate(control) {
3194
+ console.log('🔍 [FileInput] validate() called - uploadStatus:', this.uploadStatus, 'required:', this.required, 'files:', !!this.files, 'control.value:', control.value);
3195
+ // If upload is in progress (start or uploading status), return validation error
3196
+ if (this.uploadStatus === 'start' || this.uploadStatus === 'uploading') {
3197
+ console.log('⚠️ [FileInput] Validation ERROR: Upload in progress');
3198
+ return { 'uploadInProgress': { message: 'File upload in progress. Please wait...' } };
3199
+ }
3200
+ // If required and no file is selected and no control value (uploaded file ID), return validation error
3201
+ if (this.required && !this.files && !control.value) {
3202
+ console.log('⚠️ [FileInput] Validation ERROR: File required');
3203
+ return { 'required': { message: 'Please select a file to upload.' } };
3204
+ }
3205
+ console.log('✅ [FileInput] Validation PASSED: No errors');
3206
+ return null; // No validation errors
3207
+ }
3056
3208
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3057
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CideEleFileInputComponent, isStandalone: true, selector: "cide-ele-file-input", inputs: { label: "label", accept: "accept", multiple: "multiple", disabled: "disabled", helperText: "helperText", errorText: "errorText", showPreview: "showPreview", previewWidth: "previewWidth", previewHeight: "previewHeight", previewBoxMode: "previewBoxMode", showFileName: "showFileName", placeholderText: "placeholderText", placeholderIcon: "placeholderIcon", autoUpload: "autoUpload", uploadData: "uploadData" }, outputs: { fileChange: "fileChange", uploadSuccess: "uploadSuccess", uploadError: "uploadError", uploadProgressChange: "uploadProgressChange" }, providers: [
3209
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CideEleFileInputComponent, isStandalone: true, selector: "cide-ele-file-input", inputs: { label: "label", accept: "accept", multiple: "multiple", disabled: "disabled", required: "required", helperText: "helperText", errorText: "errorText", showPreview: "showPreview", previewWidth: "previewWidth", previewHeight: "previewHeight", previewBoxMode: "previewBoxMode", showFileName: "showFileName", placeholderText: "placeholderText", placeholderIcon: "placeholderIcon", autoUpload: "autoUpload", uploadData: "uploadData" }, outputs: { fileChange: "fileChange", uploadSuccess: "uploadSuccess", uploadError: "uploadError", uploadProgressChange: "uploadProgressChange" }, providers: [
3058
3210
  {
3059
3211
  provide: NG_VALUE_ACCESSOR,
3060
3212
  useExisting: forwardRef(() => CideEleFileInputComponent),
3061
3213
  multi: true
3214
+ },
3215
+ {
3216
+ provide: NG_VALIDATORS,
3217
+ useExisting: forwardRef(() => CideEleFileInputComponent),
3218
+ multi: true
3062
3219
  }
3063
- ], ngImport: i0, template: "<div class=\"cide-file-input\">\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\n <label *ngIf=\"label && !isPreviewBoxMode()\" class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id\">{{ label }}</label>\n \n <!-- Preview Box Mode -->\n <div *ngIf=\"isPreviewBoxMode()\" class=\"cide-file-input-preview-box-container\">\n <!-- Hidden file input -->\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id\"\n [attr.accept]=\"accept\"\n [attr.multiple]=\"multiple ? true : null\"\n [disabled]=\"disabled\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-hidden\"\n />\n \n <!-- Preview Box -->\n <div \n class=\"cide-file-input-preview-box\"\n [class.cide-file-input-preview-box-disabled]=\"disabled\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [style.width]=\"previewWidth\"\n [style.height]=\"previewHeight\"\n (click)=\"triggerFileSelect()\"\n [attr.title]=\"disabled ? 'File selection disabled' : placeholderText\">\n \n <!-- No Image State -->\n <div *ngIf=\"!hasImages()\" class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ placeholderIcon }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">{{ placeholderText }}</div>\n </div>\n \n <!-- Image Preview State -->\n <div *ngIf=\"hasImages()\" class=\"cide-file-input-preview-box-content\">\n <img \n [src]=\"previewUrls[0]\" \n [alt]=\"fileNames[0] || 'Preview image'\"\n class=\"cide-file-input-preview-box-image\">\n <div class=\"cide-file-input-preview-box-overlay\">\n <div class=\"cide-file-input-preview-box-overlay-text\">Click to change</div>\n </div>\n <button \n *ngIf=\"!disabled\"\n type=\"button\" \n class=\"cide-file-input-preview-box-remove\"\n (click)=\"clearFiles(); $event.stopPropagation()\"\n title=\"Remove image\">\n \u00D7\n </button>\n </div>\n </div>\n \n <!-- File name display for preview box mode -->\n <div *ngIf=\"hasImages() && fileNames.length && showFileName\" class=\"cide-file-input-preview-box-filename\">\n {{ fileNames[0] }}\n </div>\n </div>\n\n <!-- Standard Mode -->\n <div *ngIf=\"!isPreviewBoxMode()\" class=\"cide-file-input-wrapper\">\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id\"\n [attr.accept]=\"accept\"\n [attr.multiple]=\"multiple ? true : null\"\n [disabled]=\"disabled\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-element\"\n />\n <button *ngIf=\"fileNames.length\" type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n </div>\n <div *ngIf=\"fileNames.length && !isPreviewBoxMode()\" class=\"cide-file-input-files\">\n <span *ngFor=\"let name of fileNames\">{{ name }}</span>\n </div>\n \n <!-- Image Preview Section (only for standard mode) -->\n <div *ngIf=\"isImagePreviewAvailable() && !isPreviewBoxMode()\" class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n <div \n *ngFor=\"let previewUrl of previewUrls; let i = index\" \n class=\"cide-file-input-preview-item\"\n [style.width]=\"previewWidth\"\n [style.height]=\"previewHeight\">\n <button \n type=\"button\" \n class=\"cide-file-input-preview-remove\"\n (click)=\"removePreview(i)\"\n title=\"Remove image\">\n \u00D7\n </button>\n <img \n [src]=\"previewUrl\" \n [alt]=\"fileNames[i] || 'Preview image'\"\n class=\"cide-file-input-preview-image\"\n loading=\"lazy\">\n <div class=\"cide-file-input-preview-filename\">{{ fileNames[i] }}</div>\n </div>\n </div>\n </div>\n \n <div *ngIf=\"errorText\" class=\"cide-file-input-error\">{{ errorText }}</div>\n <div *ngIf=\"helperText && !errorText\" class=\"cide-file-input-helper\">{{ helperText }}</div>\n</div> ", styles: [".cide-file-input{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-label{font-weight:500;margin-bottom:.25rem}.cide-file-input-wrapper{display:flex;align-items:center;gap:.5rem}.cide-file-input-element{flex:1}.cide-file-input-clear{background:none;border:none;color:#d32f2f;cursor:pointer;font-size:.9rem}.cide-file-input-files{font-size:.95rem;color:#333;margin-top:.25rem}.cide-file-input-error{color:#d32f2f;font-size:.9rem}.cide-file-input-helper{color:#666;font-size:.9rem}.cide-file-input-preview{margin-top:.75rem;padding:.75rem;background-color:#f8f9fa;border:1px solid #e1e5e9;border-radius:.375rem}.cide-file-input-preview-label{font-weight:500;margin-bottom:.5rem;color:#374151;font-size:.875rem}.cide-file-input-preview-container{display:flex;flex-wrap:wrap;gap:.75rem}.cide-file-input-preview-item{position:relative;display:flex;flex-direction:column;border:1px solid #d1d5db;border-radius:.5rem;overflow:hidden;background-color:#fff;box-shadow:0 1px 3px #0000001a;transition:box-shadow .2s ease-in-out}.cide-file-input-preview-item:hover{box-shadow:0 4px 6px -1px #0000001a}.cide-file-input-preview-image{width:100%;height:calc(100% - 2rem);object-fit:cover;object-position:center;background-color:#f3f4f6}.cide-file-input-preview-filename{padding:.375rem .5rem;background-color:#f9fafbf2;border-top:1px solid #e5e7eb;font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:2rem;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-remove{position:absolute;top:.25rem;right:.25rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:10;transition:all .2s ease-in-out}.cide-file-input-preview-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-hidden{display:none}.cide-file-input-preview-box-container{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-preview-box{position:relative;border:2px dashed #d1d5db;border-radius:.5rem;cursor:pointer;background-color:#f9fafb;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:all .2s ease-in-out}.cide-file-input-preview-box:hover{border-color:#3b82f6;background-color:#eff6ff}.cide-file-input-preview-box.cide-file-input-preview-box-disabled{cursor:not-allowed;opacity:.6;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-disabled:hover{border-color:#d1d5db;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image{border-style:solid;border-color:#e5e7eb;padding:0}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover{border-color:#3b82f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover .cide-file-input-preview-box-overlay{opacity:1}.cide-file-input-preview-box-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1rem;text-align:center}.cide-file-input-preview-box-icon{font-size:2rem;color:#6b7280}.cide-file-input-preview-box-text{font-size:.875rem;color:#6b7280;font-weight:500}.cide-file-input-preview-box-content{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-box-image{width:100%;height:100%;object-fit:cover;object-position:center}.cide-file-input-preview-box-overlay{position:absolute;inset:0;background-color:#0009;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s ease-in-out}.cide-file-input-preview-box-overlay-text{color:#fff;font-size:.875rem;font-weight:500;text-align:center}.cide-file-input-preview-box-remove{position:absolute;top:.375rem;right:.375rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:20;transition:all .2s ease-in-out}.cide-file-input-preview-box-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-box-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-preview-box-filename{font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;margin-top:.25rem}@media (max-width: 640px){.cide-file-input-preview-container{justify-content:center}.cide-file-input-preview-item{min-width:120px;max-width:150px}.cide-file-input-preview-box-icon{font-size:1.5rem}.cide-file-input-preview-box-text{font-size:.75rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
3220
+ ], ngImport: i0, template: "<div class=\"cide-file-input\">\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\n <label *ngIf=\"label && !isPreviewBoxMode()\" class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id\">\n {{ label }}<span *ngIf=\"required\" class=\"cide-file-input-required\"> *</span>\n </label>\n \n <!-- Preview Box Mode -->\n <div *ngIf=\"isPreviewBoxMode()\" class=\"cide-file-input-preview-box-container\">\n <!-- Hidden file input -->\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id\"\n [attr.accept]=\"accept\"\n [attr.multiple]=\"multiple ? true : null\"\n [disabled]=\"disabled\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-hidden\"\n />\n \n <!-- Preview Box -->\n <div \n class=\"cide-file-input-preview-box\"\n [class.cide-file-input-preview-box-disabled]=\"disabled\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [style.width]=\"previewWidth\"\n [style.height]=\"previewHeight\"\n (click)=\"triggerFileSelect()\"\n [attr.title]=\"disabled ? 'File selection disabled' : placeholderText\">\n \n <!-- No Image State -->\n <div *ngIf=\"!hasImages()\" class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ placeholderIcon }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">{{ placeholderText }}</div>\n </div>\n \n <!-- Image Preview State -->\n <div *ngIf=\"hasImages()\" class=\"cide-file-input-preview-box-content\">\n <img \n [src]=\"previewUrls[0]\" \n [alt]=\"fileNames[0] || 'Preview image'\"\n class=\"cide-file-input-preview-box-image\">\n <div class=\"cide-file-input-preview-box-overlay\">\n <div class=\"cide-file-input-preview-box-overlay-text\">Click to change</div>\n </div>\n <button \n *ngIf=\"!disabled\"\n type=\"button\" \n class=\"cide-file-input-preview-box-remove\"\n (click)=\"clearFiles(); $event.stopPropagation()\"\n title=\"Remove image\">\n \u00D7\n </button>\n </div>\n </div>\n \n <!-- File name display for preview box mode -->\n <div *ngIf=\"hasImages() && fileNames.length && showFileName\" class=\"cide-file-input-preview-box-filename\">\n {{ fileNames[0] }}\n </div>\n </div>\n\n <!-- Standard Mode -->\n <div *ngIf=\"!isPreviewBoxMode()\" class=\"cide-file-input-wrapper\">\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id\"\n [attr.accept]=\"accept\"\n [attr.multiple]=\"multiple ? true : null\"\n [disabled]=\"disabled\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-element\"\n />\n <button *ngIf=\"fileNames.length\" type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n </div>\n <div *ngIf=\"fileNames.length && !isPreviewBoxMode()\" class=\"cide-file-input-files\">\n <span *ngFor=\"let name of fileNames\">{{ name }}</span>\n </div>\n \n <!-- Image Preview Section (only for standard mode) -->\n <div *ngIf=\"isImagePreviewAvailable() && !isPreviewBoxMode()\" class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n <div \n *ngFor=\"let previewUrl of previewUrls; let i = index\" \n class=\"cide-file-input-preview-item\"\n [style.width]=\"previewWidth\"\n [style.height]=\"previewHeight\">\n <button \n type=\"button\" \n class=\"cide-file-input-preview-remove\"\n (click)=\"removePreview(i)\"\n title=\"Remove image\">\n \u00D7\n </button>\n <img \n [src]=\"previewUrl\" \n [alt]=\"fileNames[i] || 'Preview image'\"\n class=\"cide-file-input-preview-image\"\n loading=\"lazy\">\n <div class=\"cide-file-input-preview-filename\">{{ fileNames[i] }}</div>\n </div>\n </div>\n </div>\n \n <div *ngIf=\"errorText\" class=\"cide-file-input-error\">{{ errorText }}</div>\n <div *ngIf=\"helperText && !errorText\" class=\"cide-file-input-helper\">{{ helperText }}</div>\n</div> ", styles: [".cide-file-input{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-label{font-weight:500;margin-bottom:.25rem}.cide-file-input-required{color:#d32f2f;font-weight:700}.cide-file-input-wrapper{display:flex;align-items:center;gap:.5rem}.cide-file-input-element{flex:1}.cide-file-input-clear{background:none;border:none;color:#d32f2f;cursor:pointer;font-size:.9rem}.cide-file-input-files{font-size:.95rem;color:#333;margin-top:.25rem}.cide-file-input-error{color:#d32f2f;font-size:.9rem}.cide-file-input-helper{color:#666;font-size:.9rem}.cide-file-input-preview{margin-top:.75rem;padding:.75rem;background-color:#f8f9fa;border:1px solid #e1e5e9;border-radius:.375rem}.cide-file-input-preview-label{font-weight:500;margin-bottom:.5rem;color:#374151;font-size:.875rem}.cide-file-input-preview-container{display:flex;flex-wrap:wrap;gap:.75rem}.cide-file-input-preview-item{position:relative;display:flex;flex-direction:column;border:1px solid #d1d5db;border-radius:.5rem;overflow:hidden;background-color:#fff;box-shadow:0 1px 3px #0000001a;transition:box-shadow .2s ease-in-out}.cide-file-input-preview-item:hover{box-shadow:0 4px 6px -1px #0000001a}.cide-file-input-preview-image{width:100%;height:calc(100% - 2rem);object-fit:cover;object-position:center;background-color:#f3f4f6}.cide-file-input-preview-filename{padding:.375rem .5rem;background-color:#f9fafbf2;border-top:1px solid #e5e7eb;font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:2rem;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-remove{position:absolute;top:.25rem;right:.25rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:10;transition:all .2s ease-in-out}.cide-file-input-preview-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-hidden{display:none}.cide-file-input-preview-box-container{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-preview-box{position:relative;border:2px dashed #d1d5db;border-radius:.5rem;cursor:pointer;background-color:#f9fafb;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:all .2s ease-in-out}.cide-file-input-preview-box:hover{border-color:#3b82f6;background-color:#eff6ff}.cide-file-input-preview-box.cide-file-input-preview-box-disabled{cursor:not-allowed;opacity:.6;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-disabled:hover{border-color:#d1d5db;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image{border-style:solid;border-color:#e5e7eb;padding:0}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover{border-color:#3b82f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover .cide-file-input-preview-box-overlay{opacity:1}.cide-file-input-preview-box-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1rem;text-align:center}.cide-file-input-preview-box-icon{font-size:2rem;color:#6b7280}.cide-file-input-preview-box-text{font-size:.875rem;color:#6b7280;font-weight:500}.cide-file-input-preview-box-content{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-box-image{width:100%;height:100%;object-fit:cover;object-position:center}.cide-file-input-preview-box-overlay{position:absolute;inset:0;background-color:#0009;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s ease-in-out}.cide-file-input-preview-box-overlay-text{color:#fff;font-size:.875rem;font-weight:500;text-align:center}.cide-file-input-preview-box-remove{position:absolute;top:.375rem;right:.375rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:20;transition:all .2s ease-in-out}.cide-file-input-preview-box-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-box-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-preview-box-filename{font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;margin-top:.25rem}@media (max-width: 640px){.cide-file-input-preview-container{justify-content:center}.cide-file-input-preview-item{min-width:120px;max-width:150px}.cide-file-input-preview-box-icon{font-size:1.5rem}.cide-file-input-preview-box-text{font-size:.75rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
3064
3221
  }
3065
3222
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, decorators: [{
3066
3223
  type: Component,
@@ -3069,8 +3226,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
3069
3226
  provide: NG_VALUE_ACCESSOR,
3070
3227
  useExisting: forwardRef(() => CideEleFileInputComponent),
3071
3228
  multi: true
3229
+ },
3230
+ {
3231
+ provide: NG_VALIDATORS,
3232
+ useExisting: forwardRef(() => CideEleFileInputComponent),
3233
+ multi: true
3072
3234
  }
3073
- ], template: "<div class=\"cide-file-input\">\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\n <label *ngIf=\"label && !isPreviewBoxMode()\" class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id\">{{ label }}</label>\n \n <!-- Preview Box Mode -->\n <div *ngIf=\"isPreviewBoxMode()\" class=\"cide-file-input-preview-box-container\">\n <!-- Hidden file input -->\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id\"\n [attr.accept]=\"accept\"\n [attr.multiple]=\"multiple ? true : null\"\n [disabled]=\"disabled\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-hidden\"\n />\n \n <!-- Preview Box -->\n <div \n class=\"cide-file-input-preview-box\"\n [class.cide-file-input-preview-box-disabled]=\"disabled\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [style.width]=\"previewWidth\"\n [style.height]=\"previewHeight\"\n (click)=\"triggerFileSelect()\"\n [attr.title]=\"disabled ? 'File selection disabled' : placeholderText\">\n \n <!-- No Image State -->\n <div *ngIf=\"!hasImages()\" class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ placeholderIcon }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">{{ placeholderText }}</div>\n </div>\n \n <!-- Image Preview State -->\n <div *ngIf=\"hasImages()\" class=\"cide-file-input-preview-box-content\">\n <img \n [src]=\"previewUrls[0]\" \n [alt]=\"fileNames[0] || 'Preview image'\"\n class=\"cide-file-input-preview-box-image\">\n <div class=\"cide-file-input-preview-box-overlay\">\n <div class=\"cide-file-input-preview-box-overlay-text\">Click to change</div>\n </div>\n <button \n *ngIf=\"!disabled\"\n type=\"button\" \n class=\"cide-file-input-preview-box-remove\"\n (click)=\"clearFiles(); $event.stopPropagation()\"\n title=\"Remove image\">\n \u00D7\n </button>\n </div>\n </div>\n \n <!-- File name display for preview box mode -->\n <div *ngIf=\"hasImages() && fileNames.length && showFileName\" class=\"cide-file-input-preview-box-filename\">\n {{ fileNames[0] }}\n </div>\n </div>\n\n <!-- Standard Mode -->\n <div *ngIf=\"!isPreviewBoxMode()\" class=\"cide-file-input-wrapper\">\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id\"\n [attr.accept]=\"accept\"\n [attr.multiple]=\"multiple ? true : null\"\n [disabled]=\"disabled\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-element\"\n />\n <button *ngIf=\"fileNames.length\" type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n </div>\n <div *ngIf=\"fileNames.length && !isPreviewBoxMode()\" class=\"cide-file-input-files\">\n <span *ngFor=\"let name of fileNames\">{{ name }}</span>\n </div>\n \n <!-- Image Preview Section (only for standard mode) -->\n <div *ngIf=\"isImagePreviewAvailable() && !isPreviewBoxMode()\" class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n <div \n *ngFor=\"let previewUrl of previewUrls; let i = index\" \n class=\"cide-file-input-preview-item\"\n [style.width]=\"previewWidth\"\n [style.height]=\"previewHeight\">\n <button \n type=\"button\" \n class=\"cide-file-input-preview-remove\"\n (click)=\"removePreview(i)\"\n title=\"Remove image\">\n \u00D7\n </button>\n <img \n [src]=\"previewUrl\" \n [alt]=\"fileNames[i] || 'Preview image'\"\n class=\"cide-file-input-preview-image\"\n loading=\"lazy\">\n <div class=\"cide-file-input-preview-filename\">{{ fileNames[i] }}</div>\n </div>\n </div>\n </div>\n \n <div *ngIf=\"errorText\" class=\"cide-file-input-error\">{{ errorText }}</div>\n <div *ngIf=\"helperText && !errorText\" class=\"cide-file-input-helper\">{{ helperText }}</div>\n</div> ", styles: [".cide-file-input{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-label{font-weight:500;margin-bottom:.25rem}.cide-file-input-wrapper{display:flex;align-items:center;gap:.5rem}.cide-file-input-element{flex:1}.cide-file-input-clear{background:none;border:none;color:#d32f2f;cursor:pointer;font-size:.9rem}.cide-file-input-files{font-size:.95rem;color:#333;margin-top:.25rem}.cide-file-input-error{color:#d32f2f;font-size:.9rem}.cide-file-input-helper{color:#666;font-size:.9rem}.cide-file-input-preview{margin-top:.75rem;padding:.75rem;background-color:#f8f9fa;border:1px solid #e1e5e9;border-radius:.375rem}.cide-file-input-preview-label{font-weight:500;margin-bottom:.5rem;color:#374151;font-size:.875rem}.cide-file-input-preview-container{display:flex;flex-wrap:wrap;gap:.75rem}.cide-file-input-preview-item{position:relative;display:flex;flex-direction:column;border:1px solid #d1d5db;border-radius:.5rem;overflow:hidden;background-color:#fff;box-shadow:0 1px 3px #0000001a;transition:box-shadow .2s ease-in-out}.cide-file-input-preview-item:hover{box-shadow:0 4px 6px -1px #0000001a}.cide-file-input-preview-image{width:100%;height:calc(100% - 2rem);object-fit:cover;object-position:center;background-color:#f3f4f6}.cide-file-input-preview-filename{padding:.375rem .5rem;background-color:#f9fafbf2;border-top:1px solid #e5e7eb;font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:2rem;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-remove{position:absolute;top:.25rem;right:.25rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:10;transition:all .2s ease-in-out}.cide-file-input-preview-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-hidden{display:none}.cide-file-input-preview-box-container{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-preview-box{position:relative;border:2px dashed #d1d5db;border-radius:.5rem;cursor:pointer;background-color:#f9fafb;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:all .2s ease-in-out}.cide-file-input-preview-box:hover{border-color:#3b82f6;background-color:#eff6ff}.cide-file-input-preview-box.cide-file-input-preview-box-disabled{cursor:not-allowed;opacity:.6;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-disabled:hover{border-color:#d1d5db;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image{border-style:solid;border-color:#e5e7eb;padding:0}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover{border-color:#3b82f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover .cide-file-input-preview-box-overlay{opacity:1}.cide-file-input-preview-box-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1rem;text-align:center}.cide-file-input-preview-box-icon{font-size:2rem;color:#6b7280}.cide-file-input-preview-box-text{font-size:.875rem;color:#6b7280;font-weight:500}.cide-file-input-preview-box-content{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-box-image{width:100%;height:100%;object-fit:cover;object-position:center}.cide-file-input-preview-box-overlay{position:absolute;inset:0;background-color:#0009;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s ease-in-out}.cide-file-input-preview-box-overlay-text{color:#fff;font-size:.875rem;font-weight:500;text-align:center}.cide-file-input-preview-box-remove{position:absolute;top:.375rem;right:.375rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:20;transition:all .2s ease-in-out}.cide-file-input-preview-box-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-box-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-preview-box-filename{font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;margin-top:.25rem}@media (max-width: 640px){.cide-file-input-preview-container{justify-content:center}.cide-file-input-preview-item{min-width:120px;max-width:150px}.cide-file-input-preview-box-icon{font-size:1.5rem}.cide-file-input-preview-box-text{font-size:.75rem}}\n"] }]
3235
+ ], template: "<div class=\"cide-file-input\">\n <!-- Label (shown when not in preview box mode or when preview box mode but no label override) -->\n <label *ngIf=\"label && !isPreviewBoxMode()\" class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id\">\n {{ label }}<span *ngIf=\"required\" class=\"cide-file-input-required\"> *</span>\n </label>\n \n <!-- Preview Box Mode -->\n <div *ngIf=\"isPreviewBoxMode()\" class=\"cide-file-input-preview-box-container\">\n <!-- Hidden file input -->\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id\"\n [attr.accept]=\"accept\"\n [attr.multiple]=\"multiple ? true : null\"\n [disabled]=\"disabled\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-hidden\"\n />\n \n <!-- Preview Box -->\n <div \n class=\"cide-file-input-preview-box\"\n [class.cide-file-input-preview-box-disabled]=\"disabled\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [style.width]=\"previewWidth\"\n [style.height]=\"previewHeight\"\n (click)=\"triggerFileSelect()\"\n [attr.title]=\"disabled ? 'File selection disabled' : placeholderText\">\n \n <!-- No Image State -->\n <div *ngIf=\"!hasImages()\" class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ placeholderIcon }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">{{ placeholderText }}</div>\n </div>\n \n <!-- Image Preview State -->\n <div *ngIf=\"hasImages()\" class=\"cide-file-input-preview-box-content\">\n <img \n [src]=\"previewUrls[0]\" \n [alt]=\"fileNames[0] || 'Preview image'\"\n class=\"cide-file-input-preview-box-image\">\n <div class=\"cide-file-input-preview-box-overlay\">\n <div class=\"cide-file-input-preview-box-overlay-text\">Click to change</div>\n </div>\n <button \n *ngIf=\"!disabled\"\n type=\"button\" \n class=\"cide-file-input-preview-box-remove\"\n (click)=\"clearFiles(); $event.stopPropagation()\"\n title=\"Remove image\">\n \u00D7\n </button>\n </div>\n </div>\n \n <!-- File name display for preview box mode -->\n <div *ngIf=\"hasImages() && fileNames.length && showFileName\" class=\"cide-file-input-preview-box-filename\">\n {{ fileNames[0] }}\n </div>\n </div>\n\n <!-- Standard Mode -->\n <div *ngIf=\"!isPreviewBoxMode()\" class=\"cide-file-input-wrapper\">\n <input\n type=\"file\"\n [attr.id]=\"'cide-file-input-' + id\"\n [attr.accept]=\"accept\"\n [attr.multiple]=\"multiple ? true : null\"\n [disabled]=\"disabled\"\n (change)=\"onFileSelected($event)\"\n class=\"cide-file-input-element\"\n />\n <button *ngIf=\"fileNames.length\" type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n </div>\n <div *ngIf=\"fileNames.length && !isPreviewBoxMode()\" class=\"cide-file-input-files\">\n <span *ngFor=\"let name of fileNames\">{{ name }}</span>\n </div>\n \n <!-- Image Preview Section (only for standard mode) -->\n <div *ngIf=\"isImagePreviewAvailable() && !isPreviewBoxMode()\" class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n <div \n *ngFor=\"let previewUrl of previewUrls; let i = index\" \n class=\"cide-file-input-preview-item\"\n [style.width]=\"previewWidth\"\n [style.height]=\"previewHeight\">\n <button \n type=\"button\" \n class=\"cide-file-input-preview-remove\"\n (click)=\"removePreview(i)\"\n title=\"Remove image\">\n \u00D7\n </button>\n <img \n [src]=\"previewUrl\" \n [alt]=\"fileNames[i] || 'Preview image'\"\n class=\"cide-file-input-preview-image\"\n loading=\"lazy\">\n <div class=\"cide-file-input-preview-filename\">{{ fileNames[i] }}</div>\n </div>\n </div>\n </div>\n \n <div *ngIf=\"errorText\" class=\"cide-file-input-error\">{{ errorText }}</div>\n <div *ngIf=\"helperText && !errorText\" class=\"cide-file-input-helper\">{{ helperText }}</div>\n</div> ", styles: [".cide-file-input{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-label{font-weight:500;margin-bottom:.25rem}.cide-file-input-required{color:#d32f2f;font-weight:700}.cide-file-input-wrapper{display:flex;align-items:center;gap:.5rem}.cide-file-input-element{flex:1}.cide-file-input-clear{background:none;border:none;color:#d32f2f;cursor:pointer;font-size:.9rem}.cide-file-input-files{font-size:.95rem;color:#333;margin-top:.25rem}.cide-file-input-error{color:#d32f2f;font-size:.9rem}.cide-file-input-helper{color:#666;font-size:.9rem}.cide-file-input-preview{margin-top:.75rem;padding:.75rem;background-color:#f8f9fa;border:1px solid #e1e5e9;border-radius:.375rem}.cide-file-input-preview-label{font-weight:500;margin-bottom:.5rem;color:#374151;font-size:.875rem}.cide-file-input-preview-container{display:flex;flex-wrap:wrap;gap:.75rem}.cide-file-input-preview-item{position:relative;display:flex;flex-direction:column;border:1px solid #d1d5db;border-radius:.5rem;overflow:hidden;background-color:#fff;box-shadow:0 1px 3px #0000001a;transition:box-shadow .2s ease-in-out}.cide-file-input-preview-item:hover{box-shadow:0 4px 6px -1px #0000001a}.cide-file-input-preview-image{width:100%;height:calc(100% - 2rem);object-fit:cover;object-position:center;background-color:#f3f4f6}.cide-file-input-preview-filename{padding:.375rem .5rem;background-color:#f9fafbf2;border-top:1px solid #e5e7eb;font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:2rem;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-remove{position:absolute;top:.25rem;right:.25rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:10;transition:all .2s ease-in-out}.cide-file-input-preview-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-hidden{display:none}.cide-file-input-preview-box-container{display:flex;flex-direction:column;gap:.5rem}.cide-file-input-preview-box{position:relative;border:2px dashed #d1d5db;border-radius:.5rem;cursor:pointer;background-color:#f9fafb;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:all .2s ease-in-out}.cide-file-input-preview-box:hover{border-color:#3b82f6;background-color:#eff6ff}.cide-file-input-preview-box.cide-file-input-preview-box-disabled{cursor:not-allowed;opacity:.6;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-disabled:hover{border-color:#d1d5db;background-color:#f3f4f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image{border-style:solid;border-color:#e5e7eb;padding:0}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover{border-color:#3b82f6}.cide-file-input-preview-box.cide-file-input-preview-box-has-image:hover .cide-file-input-preview-box-overlay{opacity:1}.cide-file-input-preview-box-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1rem;text-align:center}.cide-file-input-preview-box-icon{font-size:2rem;color:#6b7280}.cide-file-input-preview-box-text{font-size:.875rem;color:#6b7280;font-weight:500}.cide-file-input-preview-box-content{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.cide-file-input-preview-box-image{width:100%;height:100%;object-fit:cover;object-position:center}.cide-file-input-preview-box-overlay{position:absolute;inset:0;background-color:#0009;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s ease-in-out}.cide-file-input-preview-box-overlay-text{color:#fff;font-size:.875rem;font-weight:500;text-align:center}.cide-file-input-preview-box-remove{position:absolute;top:.375rem;right:.375rem;width:1.5rem;height:1.5rem;background-color:#ef4444e6;color:#fff;border:none;border-radius:50%;font-size:1rem;font-weight:700;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:20;transition:all .2s ease-in-out}.cide-file-input-preview-box-remove:hover{background-color:#dc2626f2;transform:scale(1.1)}.cide-file-input-preview-box-remove:focus{outline:2px solid #3b82f6;outline-offset:2px}.cide-file-input-preview-box-filename{font-size:.75rem;color:#374151;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;margin-top:.25rem}@media (max-width: 640px){.cide-file-input-preview-container{justify-content:center}.cide-file-input-preview-item{min-width:120px;max-width:150px}.cide-file-input-preview-box-icon{font-size:1.5rem}.cide-file-input-preview-box-text{font-size:.75rem}}\n"] }]
3074
3236
  }], propDecorators: { label: [{
3075
3237
  type: Input
3076
3238
  }], accept: [{
@@ -3079,6 +3241,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
3079
3241
  type: Input
3080
3242
  }], disabled: [{
3081
3243
  type: Input
3244
+ }], required: [{
3245
+ type: Input
3082
3246
  }], helperText: [{
3083
3247
  type: Input
3084
3248
  }], errorText: [{