cloud-ide-element 1.0.56 → 1.0.57

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);
2647
2796
  }
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 });
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);
2846
+ }
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,6 +3130,26 @@ 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
3154
  onChange = (files) => { };
2915
3155
  onTouched = () => { };
@@ -2918,6 +3158,23 @@ class CideEleFileInputComponent {
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" }] : []));
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
+ }
2921
3178
  writeValue(files) {
2922
3179
  console.log('📝 [FileInput] writeValue called with:', files ? Array.from(files).map(f => f.name) : 'null');
2923
3180
  this.files.set(files);
@@ -2975,6 +3232,9 @@ class CideEleFileInputComponent {
2975
3232
  }
2976
3233
  uploadFile(file) {
2977
3234
  console.log('📤 [FileInput] uploadFile called for:', file.name, 'Size:', file.size, 'bytes');
3235
+ // Angular 20: Use PendingTasks for better loading state management
3236
+ // const uploadTask = this.pendingTasks.add(); // TODO: Fix PendingTasks API usage
3237
+ // console.log('⏳ [FileInput] Pending task added for upload tracking');
2978
3238
  // Set upload status to 'start' before starting upload
2979
3239
  this.uploadStatus.set('start');
2980
3240
  console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
@@ -3031,6 +3291,9 @@ class CideEleFileInputComponent {
3031
3291
  }).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
3032
3292
  next: (response) => {
3033
3293
  console.log('🎉 [FileInput] Upload SUCCESS - Response received:', response);
3294
+ // Angular 20: Complete the pending task
3295
+ // this.pendingTasks.complete(uploadTask); // TODO: Fix PendingTasks API usage
3296
+ // console.log('✅ [FileInput] Pending task completed for successful upload');
3034
3297
  // Set upload status to 'success'
3035
3298
  this.uploadStatus.set('success');
3036
3299
  console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
@@ -3046,14 +3309,13 @@ class CideEleFileInputComponent {
3046
3309
  }
3047
3310
  this.notificationService.success('✅ File uploaded successfully!', { duration: 0 });
3048
3311
  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;
3312
+ // Extract ID from CoreFileManagerInsertUpdateResponse
3313
+ const uploadedId = response?.data?.core_file_manager?.[0]?.cyfm_id;
3051
3314
  if (uploadedId) {
3052
3315
  console.log('✅ [FileInput] File uploaded successfully with ID:', uploadedId);
3053
3316
  this.uploadSuccess.emit(uploadedId);
3054
- // Update the form control value with the uploaded file ID
3055
- this.onChange(uploadedId);
3056
- console.log('📝 [FileInput] Form control value updated with uploaded file ID');
3317
+ // Note: Form control value remains as FileList - uploaded ID is emitted separately
3318
+ console.log('📝 [FileInput] Upload success event emitted with file ID:', uploadedId);
3057
3319
  }
3058
3320
  else {
3059
3321
  console.error('❌ [FileInput] Upload successful but no ID returned:', response);
@@ -3067,6 +3329,9 @@ class CideEleFileInputComponent {
3067
3329
  },
3068
3330
  error: (error) => {
3069
3331
  console.error('💥 [FileInput] Upload FAILED:', error);
3332
+ // Angular 20: Complete the pending task even on error
3333
+ // this.pendingTasks.complete(uploadTask); // TODO: Fix PendingTasks API usage
3334
+ // console.log('❌ [FileInput] Pending task completed for failed upload');
3070
3335
  // Set upload status to 'error' and remove upload validation error
3071
3336
  this.uploadStatus.set('error');
3072
3337
  console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
@@ -3158,9 +3423,120 @@ class CideEleFileInputComponent {
3158
3423
  fileInput.click();
3159
3424
  }
3160
3425
  }
3426
+ // Drag and Drop Event Handlers
3427
+ onDragOver(event) {
3428
+ event.preventDefault();
3429
+ event.stopPropagation();
3430
+ if (!this.disabled()) {
3431
+ this.isDragOver.set(true);
3432
+ console.log('🔄 [FileInput] Drag over detected');
3433
+ }
3434
+ }
3435
+ onDragLeave(event) {
3436
+ event.preventDefault();
3437
+ event.stopPropagation();
3438
+ this.isDragOver.set(false);
3439
+ console.log('🔄 [FileInput] Drag leave detected');
3440
+ }
3441
+ onDragEnter(event) {
3442
+ event.preventDefault();
3443
+ event.stopPropagation();
3444
+ if (!this.disabled()) {
3445
+ this.isDragOver.set(true);
3446
+ console.log('🔄 [FileInput] Drag enter detected');
3447
+ }
3448
+ }
3449
+ onDrop(event) {
3450
+ event.preventDefault();
3451
+ event.stopPropagation();
3452
+ this.isDragOver.set(false);
3453
+ if (this.disabled()) {
3454
+ console.log('⏸️ [FileInput] Drop ignored - component is disabled');
3455
+ return;
3456
+ }
3457
+ const files = event.dataTransfer?.files;
3458
+ if (files && files.length > 0) {
3459
+ console.log('📁 [FileInput] Files dropped:', Array.from(files).map(f => f.name));
3460
+ // Validate file types if accept is specified
3461
+ if (this.accept() && !this.validateFileTypes(files)) {
3462
+ console.log('❌ [FileInput] Invalid file types dropped');
3463
+ this.notificationService.error('❌ Invalid file type. Please select files of the correct type.', { duration: 0 });
3464
+ return;
3465
+ }
3466
+ // Handle single vs multiple files
3467
+ if (!this.multiple() && files.length > 1) {
3468
+ console.log('⚠️ [FileInput] Multiple files dropped but multiple is disabled');
3469
+ this.notificationService.warning('⚠️ Only one file is allowed. Using the first file.', { duration: 0 });
3470
+ // Create a new FileList with only the first file
3471
+ const dt = new DataTransfer();
3472
+ dt.items.add(files[0]);
3473
+ this.handleFileSelection(dt.files);
3474
+ }
3475
+ else {
3476
+ this.handleFileSelection(files);
3477
+ }
3478
+ }
3479
+ }
3480
+ validateFileTypes(files) {
3481
+ const acceptTypes = this.accept().split(',').map(type => type.trim());
3482
+ if (acceptTypes.length === 0 || acceptTypes[0] === '')
3483
+ return true;
3484
+ return Array.from(files).every(file => {
3485
+ return acceptTypes.some(acceptType => {
3486
+ if (acceptType.startsWith('.')) {
3487
+ // Extension-based validation
3488
+ return file.name.toLowerCase().endsWith(acceptType.toLowerCase());
3489
+ }
3490
+ else if (acceptType.includes('/')) {
3491
+ // MIME type validation
3492
+ return file.type === acceptType || file.type.startsWith(acceptType.replace('*', ''));
3493
+ }
3494
+ return false;
3495
+ });
3496
+ });
3497
+ }
3498
+ handleFileSelection(files) {
3499
+ this.files.set(files);
3500
+ this.fileNames.set(Array.from(files).map(f => f.name));
3501
+ console.log('📁 [FileInput] Files selected via drag & drop:', this.fileNames());
3502
+ this.generatePreviews();
3503
+ // Reset upload status when new file is selected
3504
+ this.uploadStatus.set('idle');
3505
+ console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
3506
+ this.onChange(files);
3507
+ this.fileChange.emit(files);
3508
+ this.onTouched();
3509
+ this.onValidatorChange();
3510
+ // Auto upload if enabled
3511
+ if (this.autoUpload() && files.length > 0) {
3512
+ console.log('🚀 [FileInput] Auto upload enabled, starting upload for:', files[0].name);
3513
+ this.uploadFile(files[0]);
3514
+ }
3515
+ else {
3516
+ console.log('⏸️ [FileInput] Auto upload disabled or no files');
3517
+ }
3518
+ }
3161
3519
  isRequired() {
3162
3520
  return this.required();
3163
3521
  }
3522
+ /**
3523
+ * Angular 20: Utility method to get upload data with proper typing
3524
+ * @returns Properly typed upload data
3525
+ */
3526
+ getUploadData() {
3527
+ return this.uploadData();
3528
+ }
3529
+ /**
3530
+ * Angular 20: Utility method to update upload data with type safety
3531
+ * @param data Partial upload data to merge with existing data
3532
+ */
3533
+ updateUploadData(data) {
3534
+ const currentData = this.uploadData();
3535
+ const updatedData = { ...currentData, ...data };
3536
+ // Note: This would require the uploadData to be a writable signal
3537
+ // For now, this method serves as a type-safe way to work with upload data
3538
+ console.log('📝 [FileInput] Upload data updated:', updatedData);
3539
+ }
3164
3540
  getCurrentState() {
3165
3541
  return {
3166
3542
  id: this.id(),
@@ -3222,7 +3598,7 @@ class CideEleFileInputComponent {
3222
3598
  return null; // No validation errors
3223
3599
  }
3224
3600
  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: [
3601
+ 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
3602
  {
3227
3603
  provide: NG_VALUE_ACCESSOR,
3228
3604
  useExisting: CideEleFileInputComponent,
@@ -3233,7 +3609,7 @@ class CideEleFileInputComponent {
3233
3609
  useExisting: CideEleFileInputComponent,
3234
3610
  multi: true
3235
3611
  }
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"] }] });
3612
+ ], 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
3613
  }
3238
3614
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, decorators: [{
3239
3615
  type: Component,
@@ -3248,8 +3624,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
3248
3624
  useExisting: CideEleFileInputComponent,
3249
3625
  multi: true
3250
3626
  }
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
- }] });
3627
+ ], 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"] }]
3628
+ }], ctorParameters: () => [] });
3253
3629
 
3254
3630
  class CideTextareaComponent {
3255
3631
  label = '';