cloud-ide-element 1.0.56 → 1.0.58

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.
@@ -1,12 +1,12 @@
1
1
  import * as i1 from '@angular/common';
2
2
  import { CommonModule, NgTemplateOutlet } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { Pipe, Injectable, inject, EventEmitter, ViewContainerRef, forwardRef, ViewChild, Output, Input, Component, HostListener, ContentChildren, signal, computed, DestroyRef, input, output, Directive, viewChild, effect } from '@angular/core';
4
+ import { Pipe, Injectable, inject, EventEmitter, ViewContainerRef, forwardRef, ViewChild, Output, Input, Component, HostListener, ContentChildren, signal, DestroyRef, computed, input, output, linkedSignal, afterRenderEffect, afterNextRender, Directive, viewChild, effect } from '@angular/core';
5
5
  import * as i2 from '@angular/forms';
6
6
  import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
7
- import { BehaviorSubject, Subject, debounceTime, takeUntil, distinctUntilChanged, Observable } from 'rxjs';
7
+ import { BehaviorSubject, Subject, debounceTime, takeUntil, distinctUntilChanged, Observable, retry, catchError, finalize, throwError } from 'rxjs';
8
8
  import * as i2$1 from '@angular/router';
9
- import * as i1$1 from '@angular/common/http';
9
+ import { HttpClient, HttpEventType } from '@angular/common/http';
10
10
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
11
11
 
12
12
  class CapitalizePipe {
@@ -2564,88 +2564,307 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
2564
2564
  type: Output
2565
2565
  }] } });
2566
2566
 
2567
+ class ICoreCyfm {
2568
+ _id;
2569
+ cyfm_name;
2570
+ cyfm_alt_text;
2571
+ cyfm_path;
2572
+ cyfm_size_in_byte;
2573
+ cyfm_type;
2574
+ cyfm_creation_dt;
2575
+ cyfm_id_user;
2576
+ cyfm_permissions;
2577
+ cyfm_tags;
2578
+ cyfm_version;
2579
+ cyfm_file_status_sygmt;
2580
+ cyfm_isactive;
2581
+ }
2582
+ /* INTERFACE END */
2583
+ /* MODEL START */
2584
+ class MFileManager {
2585
+ cyfm_id = "";
2586
+ constructor(init) {
2587
+ Object.assign(this, init);
2588
+ }
2589
+ Validate() {
2590
+ let errorLogger = {};
2591
+ // Log errors here
2592
+ if (!this.cyfm_id) {
2593
+ errorLogger.cyfm_id = "File ID is Required!";
2594
+ }
2595
+ return errorLogger;
2596
+ }
2597
+ }
2598
+ class ICoreCyfmSave extends ICoreCyfm {
2599
+ cyfm_file_base64 = "";
2600
+ cyfm_temp_unique_id = "";
2601
+ constructor(init) {
2602
+ super();
2603
+ Object.assign(this, init);
2604
+ }
2605
+ }
2606
+ class CoreFileManagerInsertUpdatePayload {
2607
+ core_file_manager_new = [];
2608
+ core_file_manager_update = [];
2609
+ core_file_manager_delete = [];
2610
+ constructor(init) {
2611
+ Object.assign(this, init);
2612
+ }
2613
+ Validate() {
2614
+ let errorLogger = {};
2615
+ // Log errors here
2616
+ return errorLogger;
2617
+ }
2618
+ }
2619
+
2567
2620
  class CideEleFileManagerService {
2568
- http;
2569
- baseUrl = '/api'; // This will be configured by the consuming application
2570
- constructor(http) {
2571
- this.http = http;
2621
+ // Angular 20: Modern dependency injection
2622
+ http = inject(HttpClient);
2623
+ destroyRef = inject(DestroyRef);
2624
+ // Angular 20: Signal-based state management
2625
+ _baseUrl = signal('/api', ...(ngDevMode ? [{ debugName: "_baseUrl" }] : []));
2626
+ _isUploading = signal(false, ...(ngDevMode ? [{ debugName: "_isUploading" }] : []));
2627
+ _uploadQueue = signal([], ...(ngDevMode ? [{ debugName: "_uploadQueue" }] : []));
2628
+ _activeUploads = signal(new Map(), ...(ngDevMode ? [{ debugName: "_activeUploads" }] : []));
2629
+ _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
2630
+ // Angular 20: Computed values
2631
+ baseUrl = this._baseUrl.asReadonly();
2632
+ isUploading = this._isUploading.asReadonly();
2633
+ uploadQueue = this._uploadQueue.asReadonly();
2634
+ activeUploads = this._activeUploads.asReadonly();
2635
+ error = this._error.asReadonly();
2636
+ hasActiveUploads = computed(() => this._activeUploads().size > 0, ...(ngDevMode ? [{ debugName: "hasActiveUploads" }] : []));
2637
+ queueLength = computed(() => this._uploadQueue().length, ...(ngDevMode ? [{ debugName: "queueLength" }] : []));
2638
+ serviceState = computed(() => ({
2639
+ isUploading: this._isUploading(),
2640
+ uploadQueue: this._uploadQueue(),
2641
+ activeUploads: this._activeUploads(),
2642
+ error: this._error()
2643
+ }), ...(ngDevMode ? [{ debugName: "serviceState" }] : []));
2644
+ constructor() {
2645
+ console.log('🚀 [FileManagerService] Angular 20 service initialized');
2572
2646
  }
2573
2647
  /**
2574
2648
  * Upload a file with base64 encoding and progress tracking
2649
+ * Angular 20: Enhanced with better error handling, retry logic, and signal-based state
2575
2650
  * @param file The file to upload
2576
- * @param additionalData Additional data to include in the payload
2651
+ * @param options Upload options and additional data
2577
2652
  * @param progressCallback Optional callback for progress updates
2578
2653
  * @returns Observable with the upload response
2579
2654
  */
2580
- uploadFile(file, additionalData, progressCallback) {
2581
- // Convert file to base64 with progress tracking
2655
+ uploadFile(file, options, progressCallback) {
2656
+ const fileId = this.generateFileId(file);
2657
+ const uploadOptions = { retryAttempts: 3, timeout: 30000, ...options };
2658
+ // Angular 20: Update service state
2659
+ this._isUploading.set(true);
2660
+ this._error.set(null);
2661
+ this.addToUploadQueue(fileId);
2662
+ console.log('📤 [FileManagerService] Starting upload for:', file.name, 'Size:', file.size, 'bytes');
2582
2663
  return new Observable(observer => {
2583
2664
  const reader = new FileReader();
2584
- // Track file reading progress
2665
+ // Angular 20: Enhanced progress tracking with signals
2585
2666
  reader.onprogress = (event) => {
2586
2667
  if (event.lengthComputable && progressCallback) {
2587
2668
  const progress = (event.loaded / event.total) * 50; // File reading is 50% of total progress
2588
2669
  progressCallback(progress);
2670
+ this.updateUploadProgress(fileId, {
2671
+ loaded: event.loaded,
2672
+ total: event.total,
2673
+ percentage: progress,
2674
+ stage: 'reading'
2675
+ });
2589
2676
  }
2590
2677
  };
2591
2678
  reader.onload = () => {
2592
- const base64String = reader.result;
2593
- const base64Data = base64String.split(',')[1]; // Remove data:image/jpeg;base64, prefix
2594
- // Update progress to 50% (file reading complete)
2595
- if (progressCallback) {
2596
- progressCallback(50);
2597
- }
2598
- const payload = {
2599
- cyfm_name: file.name,
2600
- cyfm_alt_text: additionalData?.altText || file.name,
2601
- cyfm_path: '', // Will be set by backend
2602
- cyfm_size_in_byte: file.size,
2603
- cyfm_type: file.type,
2604
- cyfm_creation_dt: new Date().toISOString(),
2605
- cyfm_id_user: additionalData?.userId || '',
2606
- cyfm_permissions: additionalData?.permissions || [],
2607
- cyfm_tags: additionalData?.tags || [],
2608
- cyfm_version: 1,
2609
- cyfm_file_status_sygmt: additionalData?.fileStatus || 'active',
2610
- cyfm_isactive: true,
2611
- fileData: base64Data, // Base64 file data
2612
- ...additionalData
2613
- };
2614
- // Make the HTTP request with progress tracking
2615
- this.http.post(`${this.baseUrl}`, payload, {
2616
- reportProgress: true,
2617
- observe: 'events'
2618
- }).subscribe({
2619
- next: (event) => {
2620
- if (event.type === 1 && progressCallback) { // HttpEventType.UploadProgress
2621
- const uploadProgress = (event.loaded / event.total) * 50; // Upload is remaining 50%
2622
- const totalProgress = 50 + uploadProgress; // Total progress (50% file reading + upload progress)
2623
- progressCallback(totalProgress);
2679
+ try {
2680
+ const base64String = reader.result;
2681
+ const base64Data = base64String.split(',')[1]; // Remove data:image/jpeg;base64, prefix
2682
+ // Update progress to 50% (file reading complete)
2683
+ if (progressCallback) {
2684
+ progressCallback(50);
2685
+ }
2686
+ this.updateUploadProgress(fileId, {
2687
+ loaded: 50,
2688
+ total: 100,
2689
+ percentage: 50,
2690
+ stage: 'uploading'
2691
+ });
2692
+ // Create the file data object with proper typing
2693
+ const fileData = {
2694
+ cyfm_name: file.name,
2695
+ cyfm_alt_text: uploadOptions?.altText || file.name,
2696
+ cyfm_path: '', // Will be set by backend
2697
+ cyfm_size_in_byte: file.size,
2698
+ cyfm_type: file.type,
2699
+ cyfm_creation_dt: new Date().toISOString(),
2700
+ cyfm_id_user: uploadOptions?.userId || '',
2701
+ cyfm_permissions: [], // Match the interface type
2702
+ cyfm_tags: uploadOptions?.tags || [],
2703
+ cyfm_version: 1,
2704
+ cyfm_file_status_sygmt: uploadOptions?.fileStatus || 'file_manager_file_status_active',
2705
+ cyfm_isactive: true,
2706
+ cyfm_file_base64: base64Data,
2707
+ cyfm_temp_unique_id: fileId
2708
+ };
2709
+ // Create the payload with proper typing
2710
+ const payload = new CoreFileManagerInsertUpdatePayload({
2711
+ core_file_manager_new: [fileData],
2712
+ core_file_manager_update: [],
2713
+ core_file_manager_delete: []
2714
+ });
2715
+ // Angular 20: Enhanced HTTP request with retry logic and better error handling
2716
+ // Use proper API endpoint from cloud-ide-lms-model
2717
+ this.http.post(`${this._baseUrl()}`, payload, {
2718
+ reportProgress: true,
2719
+ observe: 'events'
2720
+ }).pipe(retry(uploadOptions.retryAttempts || 3), catchError(this.handleError.bind(this)), finalize(() => {
2721
+ this.removeFromUploadQueue(fileId);
2722
+ this.removeActiveUpload(fileId);
2723
+ if (this._uploadQueue().length === 0) {
2724
+ this._isUploading.set(false);
2624
2725
  }
2625
- else if (event.type === 4) { // HttpEventType.Response
2626
- if (progressCallback) {
2627
- progressCallback(100);
2726
+ }), takeUntilDestroyed(this.destroyRef)).subscribe({
2727
+ next: (event) => {
2728
+ if (event.type === HttpEventType.UploadProgress && progressCallback) {
2729
+ const uploadProgress = (event.loaded / event.total) * 50; // Upload is remaining 50%
2730
+ const totalProgress = 50 + uploadProgress; // Total progress (50% file reading + upload progress)
2731
+ progressCallback(totalProgress);
2732
+ this.updateUploadProgress(fileId, {
2733
+ loaded: event.loaded,
2734
+ total: event.total,
2735
+ percentage: totalProgress,
2736
+ stage: 'uploading'
2737
+ });
2738
+ }
2739
+ else if (event.type === HttpEventType.Response) {
2740
+ if (progressCallback) {
2741
+ progressCallback(100);
2742
+ }
2743
+ this.updateUploadProgress(fileId, {
2744
+ loaded: 100,
2745
+ total: 100,
2746
+ percentage: 100,
2747
+ stage: 'complete'
2748
+ });
2749
+ console.log('✅ [FileManagerService] Upload successful:', event.body);
2750
+ // Handle the CoreFileManagerInsertUpdateResponse
2751
+ const response = event.body;
2752
+ if (response.success) {
2753
+ observer.next(response);
2754
+ observer.complete();
2755
+ }
2756
+ else {
2757
+ observer.error(new Error(response.message || 'Upload failed'));
2758
+ }
2628
2759
  }
2629
- observer.next(event.body);
2760
+ },
2761
+ error: (error) => {
2762
+ console.error('❌ [FileManagerService] Upload failed:', error);
2763
+ this._error.set(error.message || 'Upload failed');
2764
+ this.updateUploadProgress(fileId, {
2765
+ loaded: 0,
2766
+ total: 100,
2767
+ percentage: 0,
2768
+ stage: 'error'
2769
+ });
2770
+ observer.error(error);
2630
2771
  }
2631
- },
2632
- error: (error) => observer.error(error)
2633
- });
2772
+ });
2773
+ }
2774
+ catch (error) {
2775
+ console.error('❌ [FileManagerService] File processing error:', error);
2776
+ this._error.set('Failed to process file');
2777
+ observer.error(error);
2778
+ }
2779
+ };
2780
+ reader.onerror = () => {
2781
+ console.error('❌ [FileManagerService] File reading failed');
2782
+ this._error.set('Failed to read file');
2783
+ observer.error(new Error('Failed to read file'));
2634
2784
  };
2635
- reader.onerror = () => observer.error('Failed to read file');
2636
2785
  reader.readAsDataURL(file);
2637
2786
  });
2638
2787
  }
2639
2788
  /**
2640
2789
  * Set the base URL for API calls
2641
- * This should be called by the consuming application during initialization
2790
+ * Angular 20: Using signal-based state management
2642
2791
  * @param url The base URL for the API
2643
2792
  */
2644
2793
  setBaseUrl(url) {
2645
2794
  console.log('🔍 [FileManagerService] Setting base URL:', url);
2646
- this.baseUrl = url;
2795
+ this._baseUrl.set(url);
2796
+ }
2797
+ /**
2798
+ * Angular 20: Helper methods for state management
2799
+ */
2800
+ generateFileId(file) {
2801
+ return `${file.name}_${file.size}_${Date.now()}`;
2802
+ }
2803
+ addToUploadQueue(fileId) {
2804
+ const currentQueue = this._uploadQueue();
2805
+ this._uploadQueue.set([...currentQueue, fileId]);
2806
+ }
2807
+ removeFromUploadQueue(fileId) {
2808
+ const currentQueue = this._uploadQueue();
2809
+ this._uploadQueue.set(currentQueue.filter(id => id !== fileId));
2810
+ }
2811
+ updateUploadProgress(fileId, progress) {
2812
+ const currentUploads = new Map(this._activeUploads());
2813
+ currentUploads.set(fileId, progress);
2814
+ this._activeUploads.set(currentUploads);
2815
+ }
2816
+ removeActiveUpload(fileId) {
2817
+ const currentUploads = new Map(this._activeUploads());
2818
+ currentUploads.delete(fileId);
2819
+ this._activeUploads.set(currentUploads);
2820
+ }
2821
+ /**
2822
+ * Angular 20: Enhanced error handling
2823
+ */
2824
+ handleError(error) {
2825
+ let errorMessage = 'An unknown error occurred';
2826
+ if (error.error instanceof ErrorEvent) {
2827
+ // Client-side error
2828
+ errorMessage = `Client Error: ${error.error.message}`;
2829
+ }
2830
+ else {
2831
+ // Server-side error
2832
+ errorMessage = `Server Error: ${error.status} - ${error.message}`;
2833
+ }
2834
+ console.error('❌ [FileManagerService] HTTP Error:', errorMessage);
2835
+ this._error.set(errorMessage);
2836
+ return throwError(() => new Error(errorMessage));
2837
+ }
2838
+ /**
2839
+ * Angular 20: Service utility methods
2840
+ */
2841
+ clearError() {
2842
+ this._error.set(null);
2843
+ }
2844
+ getUploadProgress(fileId) {
2845
+ return this._activeUploads().get(fileId);
2647
2846
  }
2648
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileManagerService, deps: [{ token: i1$1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
2847
+ cancelUpload(fileId) {
2848
+ this.removeFromUploadQueue(fileId);
2849
+ this.removeActiveUpload(fileId);
2850
+ console.log('🚫 [FileManagerService] Upload cancelled for:', fileId);
2851
+ }
2852
+ /**
2853
+ * Angular 20: File validation utility
2854
+ */
2855
+ validateFile(file, maxSizeMB = 10, allowedTypes = []) {
2856
+ // Check file size
2857
+ const maxSizeBytes = maxSizeMB * 1024 * 1024; // Convert MB to bytes
2858
+ if (file.size > maxSizeBytes) {
2859
+ return { valid: false, error: `File size exceeds ${maxSizeMB}MB limit` };
2860
+ }
2861
+ // Check file type
2862
+ if (allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
2863
+ return { valid: false, error: `File type ${file.type} is not allowed` };
2864
+ }
2865
+ return { valid: true };
2866
+ }
2867
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileManagerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2649
2868
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileManagerService, providedIn: 'root' });
2650
2869
  }
2651
2870
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileManagerService, decorators: [{
@@ -2653,7 +2872,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
2653
2872
  args: [{
2654
2873
  providedIn: 'root'
2655
2874
  }]
2656
- }], ctorParameters: () => [{ type: i1$1.HttpClient }] });
2875
+ }], ctorParameters: () => [] });
2657
2876
 
2658
2877
  class NotificationService {
2659
2878
  notifications = signal([], ...(ngDevMode ? [{ debugName: "notifications" }] : []));
@@ -2879,6 +3098,7 @@ class CideEleFileInputComponent {
2879
3098
  notificationService = inject(NotificationService);
2880
3099
  elementService = inject(CideElementsService);
2881
3100
  destroyRef = inject(DestroyRef);
3101
+ // private readonly pendingTasks = inject(PendingTasks); // TODO: Fix PendingTasks API usage
2882
3102
  // Modern input signals
2883
3103
  label = input('Choose file', ...(ngDevMode ? [{ debugName: "label" }] : []));
2884
3104
  accept = input('', ...(ngDevMode ? [{ debugName: "accept" }] : []));
@@ -2910,19 +3130,74 @@ class CideEleFileInputComponent {
2910
3130
  fileNames = signal([], ...(ngDevMode ? [{ debugName: "fileNames" }] : []));
2911
3131
  previewUrls = signal([], ...(ngDevMode ? [{ debugName: "previewUrls" }] : []));
2912
3132
  uploadNotificationId = signal(null, ...(ngDevMode ? [{ debugName: "uploadNotificationId" }] : []));
3133
+ isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
3134
+ // Angular 20: Linked signals for better relationships
3135
+ hasFiles = linkedSignal(() => this.files() !== null && this.files().length > 0);
3136
+ canUpload = linkedSignal(() => this.hasFiles() && !this.isUploading() && !this.disabled());
3137
+ isInErrorState = linkedSignal(() => this.uploadStatus() === 'error');
3138
+ isInSuccessState = linkedSignal(() => this.uploadStatus() === 'success');
3139
+ // Angular 20: Computed values using new features
3140
+ totalFileSize = computed(() => {
3141
+ if (!this.files())
3142
+ return 0;
3143
+ return Array.from(this.files()).reduce((total, file) => total + file.size, 0);
3144
+ }, ...(ngDevMode ? [{ debugName: "totalFileSize" }] : []));
3145
+ fileSizeInMB = computed(() => {
3146
+ // Angular 20: Using ** operator for exponentiation
3147
+ return (this.totalFileSize() / (1024 ** 2)).toFixed(2);
3148
+ }, ...(ngDevMode ? [{ debugName: "fileSizeInMB" }] : []));
3149
+ uploadProgressPercentage = computed(() => {
3150
+ // Angular 20: Using ** operator for exponentiation
3151
+ return Math.round(this.uploadProgress() ** 1); // Simple power operation
3152
+ }, ...(ngDevMode ? [{ debugName: "uploadProgressPercentage" }] : []));
2913
3153
  // ControlValueAccessor callbacks
2914
- onChange = (files) => { };
3154
+ onChange = (value) => { };
2915
3155
  onTouched = () => { };
2916
3156
  onValidatorChange = () => { };
2917
3157
  // Computed values
2918
3158
  hasImages = computed(() => this.previewUrls().length > 0, ...(ngDevMode ? [{ debugName: "hasImages" }] : []));
2919
3159
  isPreviewBoxMode = computed(() => this.previewBoxMode() && this.showPreview(), ...(ngDevMode ? [{ debugName: "isPreviewBoxMode" }] : []));
2920
3160
  isImagePreviewAvailable = computed(() => this.showPreview() && this.previewUrls().length > 0, ...(ngDevMode ? [{ debugName: "isImagePreviewAvailable" }] : []));
2921
- writeValue(files) {
2922
- console.log('📝 [FileInput] writeValue called with:', files ? Array.from(files).map(f => f.name) : 'null');
2923
- this.files.set(files);
2924
- this.fileNames.set(files ? Array.from(files).map(f => f.name) : []);
2925
- this.generatePreviews();
3161
+ constructor() {
3162
+ // Angular 20: afterRenderEffect for DOM operations
3163
+ afterRenderEffect(() => {
3164
+ // Update file input element when files change
3165
+ if (this.files()) {
3166
+ const fileInput = document.getElementById('cide-file-input-' + this.id());
3167
+ if (fileInput) {
3168
+ // Ensure the input reflects the current state
3169
+ fileInput.files = this.files();
3170
+ }
3171
+ }
3172
+ });
3173
+ // Angular 20: afterNextRender for one-time DOM operations
3174
+ afterNextRender(() => {
3175
+ console.log('🎯 [FileInput] Component rendered and DOM is ready');
3176
+ });
3177
+ }
3178
+ writeValue(value) {
3179
+ console.log('📝 [FileInput] writeValue called with:', value);
3180
+ if (typeof value === 'string') {
3181
+ // Value is an uploaded file ID
3182
+ console.log('📝 [FileInput] Value is uploaded file ID:', value);
3183
+ this.files.set(null);
3184
+ this.fileNames.set([]);
3185
+ this.clearPreviews();
3186
+ }
3187
+ else if (value instanceof FileList) {
3188
+ // Value is a FileList
3189
+ console.log('📝 [FileInput] Value is FileList:', Array.from(value).map(f => f.name));
3190
+ this.files.set(value);
3191
+ this.fileNames.set(Array.from(value).map(f => f.name));
3192
+ this.generatePreviews();
3193
+ }
3194
+ else {
3195
+ // Value is null
3196
+ console.log('📝 [FileInput] Value is null');
3197
+ this.files.set(null);
3198
+ this.fileNames.set([]);
3199
+ this.clearPreviews();
3200
+ }
2926
3201
  }
2927
3202
  registerOnChange(fn) {
2928
3203
  this.onChange = fn;
@@ -2952,7 +3227,6 @@ class CideEleFileInputComponent {
2952
3227
  this.onChange(selectedFiles);
2953
3228
  this.fileChange.emit(selectedFiles);
2954
3229
  this.onTouched();
2955
- this.onValidatorChange();
2956
3230
  // Auto upload if enabled
2957
3231
  if (this.autoUpload() && selectedFiles && selectedFiles.length > 0) {
2958
3232
  console.log('🚀 [FileInput] Auto upload enabled, starting upload for:', selectedFiles[0].name);
@@ -2971,16 +3245,15 @@ class CideEleFileInputComponent {
2971
3245
  console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
2972
3246
  this.onChange(null);
2973
3247
  this.fileChange.emit(null);
2974
- this.onValidatorChange();
2975
3248
  }
2976
3249
  uploadFile(file) {
2977
3250
  console.log('📤 [FileInput] uploadFile called for:', file.name, 'Size:', file.size, 'bytes');
3251
+ // Angular 20: Use PendingTasks for better loading state management
3252
+ // const uploadTask = this.pendingTasks.add(); // TODO: Fix PendingTasks API usage
3253
+ // console.log('⏳ [FileInput] Pending task added for upload tracking');
2978
3254
  // Set upload status to 'start' before starting upload
2979
3255
  this.uploadStatus.set('start');
2980
3256
  console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
2981
- // Trigger validation update to show upload in progress error
2982
- this.onValidatorChange();
2983
- console.log('✅ [FileInput] Validation triggered - should show upload in progress error');
2984
3257
  this.isUploading.set(true);
2985
3258
  this.uploadProgress.set(0);
2986
3259
  this.uploadProgressChange.emit(0);
@@ -3001,8 +3274,6 @@ class CideEleFileInputComponent {
3001
3274
  if (this.uploadStatus() === 'start') {
3002
3275
  this.uploadStatus.set('uploading');
3003
3276
  console.log('🔄 [FileInput] Upload status changed to:', this.uploadStatus());
3004
- this.onValidatorChange();
3005
- console.log('✅ [FileInput] Validation triggered after status change to uploading');
3006
3277
  }
3007
3278
  // Update progress notification with spinner
3008
3279
  const notificationId = this.uploadNotificationId();
@@ -3031,6 +3302,9 @@ class CideEleFileInputComponent {
3031
3302
  }).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
3032
3303
  next: (response) => {
3033
3304
  console.log('🎉 [FileInput] Upload SUCCESS - Response received:', response);
3305
+ // Angular 20: Complete the pending task
3306
+ // this.pendingTasks.complete(uploadTask); // TODO: Fix PendingTasks API usage
3307
+ // console.log('✅ [FileInput] Pending task completed for successful upload');
3034
3308
  // Set upload status to 'success'
3035
3309
  this.uploadStatus.set('success');
3036
3310
  console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
@@ -3046,14 +3320,15 @@ class CideEleFileInputComponent {
3046
3320
  }
3047
3321
  this.notificationService.success('✅ File uploaded successfully!', { duration: 0 });
3048
3322
  this.uploadNotificationId.set(null);
3049
- // Extract ID from response - the file manager returns the _id of the created record
3050
- const uploadedId = response?._id || response?.data?._id || response?.id || response?.fileId;
3323
+ // Extract ID from CoreFileManagerInsertUpdateResponse
3324
+ const uploadedId = response?.data?.core_file_manager?.[0]?.cyfm_id;
3051
3325
  if (uploadedId) {
3052
3326
  console.log('✅ [FileInput] File uploaded successfully with ID:', uploadedId);
3053
- this.uploadSuccess.emit(uploadedId);
3054
- // Update the form control value with the uploaded file ID
3327
+ // Set the uploaded ID as the form control value
3055
3328
  this.onChange(uploadedId);
3056
- console.log('📝 [FileInput] Form control value updated with uploaded file ID');
3329
+ console.log('📝 [FileInput] Form control value set to uploaded ID:', uploadedId);
3330
+ this.uploadSuccess.emit(uploadedId);
3331
+ console.log('📝 [FileInput] Upload success event emitted with file ID:', uploadedId);
3057
3332
  }
3058
3333
  else {
3059
3334
  console.error('❌ [FileInput] Upload successful but no ID returned:', response);
@@ -3061,12 +3336,12 @@ class CideEleFileInputComponent {
3061
3336
  }
3062
3337
  this.isUploading.set(false);
3063
3338
  console.log('🔄 [FileInput] isUploading set to false');
3064
- // Trigger validation update to clear upload in progress error
3065
- this.onValidatorChange();
3066
- console.log('✅ [FileInput] Validation triggered - should clear upload in progress error');
3067
3339
  },
3068
3340
  error: (error) => {
3069
3341
  console.error('💥 [FileInput] Upload FAILED:', error);
3342
+ // Angular 20: Complete the pending task even on error
3343
+ // this.pendingTasks.complete(uploadTask); // TODO: Fix PendingTasks API usage
3344
+ // console.log('❌ [FileInput] Pending task completed for failed upload');
3070
3345
  // Set upload status to 'error' and remove upload validation error
3071
3346
  this.uploadStatus.set('error');
3072
3347
  console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
@@ -3083,9 +3358,6 @@ class CideEleFileInputComponent {
3083
3358
  this.uploadProgress.set(0);
3084
3359
  this.uploadProgressChange.emit(0);
3085
3360
  console.log('🔄 [FileInput] Upload state reset - isUploading: false, progress: 0%');
3086
- // Trigger validation update to clear upload in progress error
3087
- this.onValidatorChange();
3088
- console.log('✅ [FileInput] Validation triggered - should clear upload in progress error');
3089
3361
  }
3090
3362
  });
3091
3363
  }
@@ -3158,9 +3430,119 @@ class CideEleFileInputComponent {
3158
3430
  fileInput.click();
3159
3431
  }
3160
3432
  }
3433
+ // Drag and Drop Event Handlers
3434
+ onDragOver(event) {
3435
+ event.preventDefault();
3436
+ event.stopPropagation();
3437
+ if (!this.disabled()) {
3438
+ this.isDragOver.set(true);
3439
+ console.log('🔄 [FileInput] Drag over detected');
3440
+ }
3441
+ }
3442
+ onDragLeave(event) {
3443
+ event.preventDefault();
3444
+ event.stopPropagation();
3445
+ this.isDragOver.set(false);
3446
+ console.log('🔄 [FileInput] Drag leave detected');
3447
+ }
3448
+ onDragEnter(event) {
3449
+ event.preventDefault();
3450
+ event.stopPropagation();
3451
+ if (!this.disabled()) {
3452
+ this.isDragOver.set(true);
3453
+ console.log('🔄 [FileInput] Drag enter detected');
3454
+ }
3455
+ }
3456
+ onDrop(event) {
3457
+ event.preventDefault();
3458
+ event.stopPropagation();
3459
+ this.isDragOver.set(false);
3460
+ if (this.disabled()) {
3461
+ console.log('⏸️ [FileInput] Drop ignored - component is disabled');
3462
+ return;
3463
+ }
3464
+ const files = event.dataTransfer?.files;
3465
+ if (files && files.length > 0) {
3466
+ console.log('📁 [FileInput] Files dropped:', Array.from(files).map(f => f.name));
3467
+ // Validate file types if accept is specified
3468
+ if (this.accept() && !this.validateFileTypes(files)) {
3469
+ console.log('❌ [FileInput] Invalid file types dropped');
3470
+ this.notificationService.error('❌ Invalid file type. Please select files of the correct type.', { duration: 0 });
3471
+ return;
3472
+ }
3473
+ // Handle single vs multiple files
3474
+ if (!this.multiple() && files.length > 1) {
3475
+ console.log('⚠️ [FileInput] Multiple files dropped but multiple is disabled');
3476
+ this.notificationService.warning('⚠️ Only one file is allowed. Using the first file.', { duration: 0 });
3477
+ // Create a new FileList with only the first file
3478
+ const dt = new DataTransfer();
3479
+ dt.items.add(files[0]);
3480
+ this.handleFileSelection(dt.files);
3481
+ }
3482
+ else {
3483
+ this.handleFileSelection(files);
3484
+ }
3485
+ }
3486
+ }
3487
+ validateFileTypes(files) {
3488
+ const acceptTypes = this.accept().split(',').map(type => type.trim());
3489
+ if (acceptTypes.length === 0 || acceptTypes[0] === '')
3490
+ return true;
3491
+ return Array.from(files).every(file => {
3492
+ return acceptTypes.some(acceptType => {
3493
+ if (acceptType.startsWith('.')) {
3494
+ // Extension-based validation
3495
+ return file.name.toLowerCase().endsWith(acceptType.toLowerCase());
3496
+ }
3497
+ else if (acceptType.includes('/')) {
3498
+ // MIME type validation
3499
+ return file.type === acceptType || file.type.startsWith(acceptType.replace('*', ''));
3500
+ }
3501
+ return false;
3502
+ });
3503
+ });
3504
+ }
3505
+ handleFileSelection(files) {
3506
+ this.files.set(files);
3507
+ this.fileNames.set(Array.from(files).map(f => f.name));
3508
+ console.log('📁 [FileInput] Files selected via drag & drop:', this.fileNames());
3509
+ this.generatePreviews();
3510
+ // Reset upload status when new file is selected
3511
+ this.uploadStatus.set('idle');
3512
+ console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
3513
+ this.onChange(files);
3514
+ this.fileChange.emit(files);
3515
+ this.onTouched();
3516
+ // Auto upload if enabled
3517
+ if (this.autoUpload() && files.length > 0) {
3518
+ console.log('🚀 [FileInput] Auto upload enabled, starting upload for:', files[0].name);
3519
+ this.uploadFile(files[0]);
3520
+ }
3521
+ else {
3522
+ console.log('⏸️ [FileInput] Auto upload disabled or no files');
3523
+ }
3524
+ }
3161
3525
  isRequired() {
3162
3526
  return this.required();
3163
3527
  }
3528
+ /**
3529
+ * Angular 20: Utility method to get upload data with proper typing
3530
+ * @returns Properly typed upload data
3531
+ */
3532
+ getUploadData() {
3533
+ return this.uploadData();
3534
+ }
3535
+ /**
3536
+ * Angular 20: Utility method to update upload data with type safety
3537
+ * @param data Partial upload data to merge with existing data
3538
+ */
3539
+ updateUploadData(data) {
3540
+ const currentData = this.uploadData();
3541
+ const updatedData = { ...currentData, ...data };
3542
+ // Note: This would require the uploadData to be a writable signal
3543
+ // For now, this method serves as a type-safe way to work with upload data
3544
+ console.log('📝 [FileInput] Upload data updated:', updatedData);
3545
+ }
3164
3546
  getCurrentState() {
3165
3547
  return {
3166
3548
  id: this.id(),
@@ -3222,7 +3604,7 @@ class CideEleFileInputComponent {
3222
3604
  return null; // No validation errors
3223
3605
  }
3224
3606
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3225
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.1.7", type: CideEleFileInputComponent, isStandalone: true, selector: "cide-ele-file-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, errorText: { classPropertyName: "errorText", publicName: "errorText", isSignal: true, isRequired: false, transformFunction: null }, showPreview: { classPropertyName: "showPreview", publicName: "showPreview", isSignal: true, isRequired: false, transformFunction: null }, previewWidth: { classPropertyName: "previewWidth", publicName: "previewWidth", isSignal: true, isRequired: false, transformFunction: null }, previewHeight: { classPropertyName: "previewHeight", publicName: "previewHeight", isSignal: true, isRequired: false, transformFunction: null }, previewBoxMode: { classPropertyName: "previewBoxMode", publicName: "previewBoxMode", isSignal: true, isRequired: false, transformFunction: null }, showFileName: { classPropertyName: "showFileName", publicName: "showFileName", isSignal: true, isRequired: false, transformFunction: null }, placeholderText: { classPropertyName: "placeholderText", publicName: "placeholderText", isSignal: true, isRequired: false, transformFunction: null }, placeholderIcon: { classPropertyName: "placeholderIcon", publicName: "placeholderIcon", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, uploadData: { classPropertyName: "uploadData", publicName: "uploadData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileChange: "fileChange", uploadSuccess: "uploadSuccess", uploadError: "uploadError", uploadProgressChange: "uploadProgressChange" }, providers: [
3607
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideEleFileInputComponent, isStandalone: true, selector: "cide-ele-file-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, errorText: { classPropertyName: "errorText", publicName: "errorText", isSignal: true, isRequired: false, transformFunction: null }, showPreview: { classPropertyName: "showPreview", publicName: "showPreview", isSignal: true, isRequired: false, transformFunction: null }, previewWidth: { classPropertyName: "previewWidth", publicName: "previewWidth", isSignal: true, isRequired: false, transformFunction: null }, previewHeight: { classPropertyName: "previewHeight", publicName: "previewHeight", isSignal: true, isRequired: false, transformFunction: null }, previewBoxMode: { classPropertyName: "previewBoxMode", publicName: "previewBoxMode", isSignal: true, isRequired: false, transformFunction: null }, showFileName: { classPropertyName: "showFileName", publicName: "showFileName", isSignal: true, isRequired: false, transformFunction: null }, placeholderText: { classPropertyName: "placeholderText", publicName: "placeholderText", isSignal: true, isRequired: false, transformFunction: null }, placeholderIcon: { classPropertyName: "placeholderIcon", publicName: "placeholderIcon", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, uploadData: { classPropertyName: "uploadData", publicName: "uploadData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileChange: "fileChange", uploadSuccess: "uploadSuccess", uploadError: "uploadError", uploadProgressChange: "uploadProgressChange" }, providers: [
3226
3608
  {
3227
3609
  provide: NG_VALUE_ACCESSOR,
3228
3610
  useExisting: CideEleFileInputComponent,
@@ -3233,7 +3615,7 @@ class CideEleFileInputComponent {
3233
3615
  useExisting: CideEleFileInputComponent,
3234
3616
  multi: true
3235
3617
  }
3236
- ], 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"] }] });
3618
+ ], 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 @if (label() && !isPreviewBoxMode()) {\n <label class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ label() }}@if (required()) {<span class=\"cide-file-input-required\"> *</span>}\n </label>\n }\n \n <!-- Preview Box Mode -->\n @if (isPreviewBoxMode()) {\n <div 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 [class.cide-file-input-preview-box-drag-over]=\"isDragOver()\"\n [style.width]=\"previewWidth()\"\n [style.height]=\"previewHeight()\"\n (click)=\"triggerFileSelect()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [attr.title]=\"disabled() ? 'File selection disabled' : placeholderText()\">\n \n <!-- No Image State -->\n @if (!hasImages()) {\n <div class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ isDragOver() ? '\uD83D\uDCC1' : placeholderIcon() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">\n {{ isDragOver() ? 'Drop files here...' : placeholderText() }}\n </div>\n </div>\n }\n \n <!-- Image Preview State -->\n @if (hasImages()) {\n <div 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 @if (!disabled()) {\n <button \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 }\n </div>\n }\n </div>\n \n <!-- File name display for preview box mode -->\n @if (hasImages() && fileNames().length && showFileName()) {\n <div class=\"cide-file-input-preview-box-filename\">\n {{ fileNames()[0] }}\n </div>\n }\n </div>\n }\n\n <!-- Standard Mode -->\n @if (!isPreviewBoxMode()) {\n <div \n class=\"cide-file-input-wrapper\"\n [class.cide-file-input-drag-over]=\"isDragOver()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\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 @if (hasFiles()) {\n <button type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n }\n </div>\n @if (hasFiles() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-files\">\n @for (name of fileNames(); track name) {\n <span>{{ name }}</span>\n }\n <!-- Angular 20: Display file size using new computed values -->\n @if (totalFileSize() > 0) {\n <div class=\"cide-file-input-size\">\n Total size: {{ fileSizeInMB() }} MB\n </div>\n }\n </div>\n }\n }\n \n <!-- Image Preview Section (only for standard mode) -->\n @if (isImagePreviewAvailable() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n @for (previewUrl of previewUrls(); track previewUrl; let i = $index) {\n <div \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 }\n </div>\n </div>\n }\n \n @if (errorText()) {\n <div class=\"cide-file-input-error\">{{ errorText() }}</div>\n }\n @if (helperText() && !errorText()) {\n <div class=\"cide-file-input-helper\">{{ helperText() }}</div>\n }\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;border:2px dashed transparent;border-radius:.5rem;padding:.5rem;transition:all .2s ease-in-out}.cide-file-input-wrapper.cide-file-input-drag-over{border-color:#3b82f6;background-color:#eff6ff;transform:scale(1.02)}.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-size{margin-top:.5rem;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;font-size:.75rem;color:#4b5563;font-weight:500}.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.cide-file-input-preview-box-drag-over{border-color:#3b82f6!important;background-color:#eff6ff!important;transform:scale(1.02);box-shadow:0 0 0 4px #3b82f61a}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-icon{color:#3b82f6;transform:scale(1.1)}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-text{color:#3b82f6;font-weight:600}.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: "ngmodule", type: FormsModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }] });
3237
3619
  }
3238
3620
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, decorators: [{
3239
3621
  type: Component,
@@ -3248,8 +3630,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
3248
3630
  useExisting: CideEleFileInputComponent,
3249
3631
  multi: true
3250
3632
  }
3251
- ], 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"] }]
3252
- }] });
3633
+ ], 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 @if (label() && !isPreviewBoxMode()) {\n <label class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ label() }}@if (required()) {<span class=\"cide-file-input-required\"> *</span>}\n </label>\n }\n \n <!-- Preview Box Mode -->\n @if (isPreviewBoxMode()) {\n <div 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 [class.cide-file-input-preview-box-drag-over]=\"isDragOver()\"\n [style.width]=\"previewWidth()\"\n [style.height]=\"previewHeight()\"\n (click)=\"triggerFileSelect()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [attr.title]=\"disabled() ? 'File selection disabled' : placeholderText()\">\n \n <!-- No Image State -->\n @if (!hasImages()) {\n <div class=\"cide-file-input-preview-box-placeholder\">\n <div class=\"cide-file-input-preview-box-icon\">\n <cide-ele-icon>{{ isDragOver() ? '\uD83D\uDCC1' : placeholderIcon() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">\n {{ isDragOver() ? 'Drop files here...' : placeholderText() }}\n </div>\n </div>\n }\n \n <!-- Image Preview State -->\n @if (hasImages()) {\n <div 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 @if (!disabled()) {\n <button \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 }\n </div>\n }\n </div>\n \n <!-- File name display for preview box mode -->\n @if (hasImages() && fileNames().length && showFileName()) {\n <div class=\"cide-file-input-preview-box-filename\">\n {{ fileNames()[0] }}\n </div>\n }\n </div>\n }\n\n <!-- Standard Mode -->\n @if (!isPreviewBoxMode()) {\n <div \n class=\"cide-file-input-wrapper\"\n [class.cide-file-input-drag-over]=\"isDragOver()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\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 @if (hasFiles()) {\n <button type=\"button\" class=\"cide-file-input-clear\" (click)=\"clearFiles()\">\n Clear\n </button>\n }\n </div>\n @if (hasFiles() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-files\">\n @for (name of fileNames(); track name) {\n <span>{{ name }}</span>\n }\n <!-- Angular 20: Display file size using new computed values -->\n @if (totalFileSize() > 0) {\n <div class=\"cide-file-input-size\">\n Total size: {{ fileSizeInMB() }} MB\n </div>\n }\n </div>\n }\n }\n \n <!-- Image Preview Section (only for standard mode) -->\n @if (isImagePreviewAvailable() && !isPreviewBoxMode()) {\n <div class=\"cide-file-input-preview\">\n <div class=\"cide-file-input-preview-label\">Preview:</div>\n <div class=\"cide-file-input-preview-container\">\n @for (previewUrl of previewUrls(); track previewUrl; let i = $index) {\n <div \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 }\n </div>\n </div>\n }\n \n @if (errorText()) {\n <div class=\"cide-file-input-error\">{{ errorText() }}</div>\n }\n @if (helperText() && !errorText()) {\n <div class=\"cide-file-input-helper\">{{ helperText() }}</div>\n }\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;border:2px dashed transparent;border-radius:.5rem;padding:.5rem;transition:all .2s ease-in-out}.cide-file-input-wrapper.cide-file-input-drag-over{border-color:#3b82f6;background-color:#eff6ff;transform:scale(1.02)}.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-size{margin-top:.5rem;padding:.25rem .5rem;background-color:#f3f4f6;border-radius:.25rem;font-size:.75rem;color:#4b5563;font-weight:500}.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.cide-file-input-preview-box-drag-over{border-color:#3b82f6!important;background-color:#eff6ff!important;transform:scale(1.02);box-shadow:0 0 0 4px #3b82f61a}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-icon{color:#3b82f6;transform:scale(1.1)}.cide-file-input-preview-box.cide-file-input-preview-box-drag-over .cide-file-input-preview-box-placeholder .cide-file-input-preview-box-text{color:#3b82f6;font-weight:600}.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"] }]
3634
+ }], ctorParameters: () => [] });
3253
3635
 
3254
3636
  class CideTextareaComponent {
3255
3637
  label = '';