cloud-ide-element 1.0.55 → 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,13 @@
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, Directive, DestroyRef, 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
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
10
11
 
11
12
  class CapitalizePipe {
12
13
  transform(value, capitalizationMethod) {
@@ -2563,88 +2564,307 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
2563
2564
  type: Output
2564
2565
  }] } });
2565
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
+
2566
2620
  class CideEleFileManagerService {
2567
- http;
2568
- baseUrl = '/api'; // This will be configured by the consuming application
2569
- constructor(http) {
2570
- 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');
2571
2646
  }
2572
2647
  /**
2573
2648
  * Upload a file with base64 encoding and progress tracking
2649
+ * Angular 20: Enhanced with better error handling, retry logic, and signal-based state
2574
2650
  * @param file The file to upload
2575
- * @param additionalData Additional data to include in the payload
2651
+ * @param options Upload options and additional data
2576
2652
  * @param progressCallback Optional callback for progress updates
2577
2653
  * @returns Observable with the upload response
2578
2654
  */
2579
- uploadFile(file, additionalData, progressCallback) {
2580
- // 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');
2581
2663
  return new Observable(observer => {
2582
2664
  const reader = new FileReader();
2583
- // Track file reading progress
2665
+ // Angular 20: Enhanced progress tracking with signals
2584
2666
  reader.onprogress = (event) => {
2585
2667
  if (event.lengthComputable && progressCallback) {
2586
2668
  const progress = (event.loaded / event.total) * 50; // File reading is 50% of total progress
2587
2669
  progressCallback(progress);
2670
+ this.updateUploadProgress(fileId, {
2671
+ loaded: event.loaded,
2672
+ total: event.total,
2673
+ percentage: progress,
2674
+ stage: 'reading'
2675
+ });
2588
2676
  }
2589
2677
  };
2590
2678
  reader.onload = () => {
2591
- const base64String = reader.result;
2592
- const base64Data = base64String.split(',')[1]; // Remove data:image/jpeg;base64, prefix
2593
- // Update progress to 50% (file reading complete)
2594
- if (progressCallback) {
2595
- progressCallback(50);
2596
- }
2597
- const payload = {
2598
- cyfm_name: file.name,
2599
- cyfm_alt_text: additionalData?.altText || file.name,
2600
- cyfm_path: '', // Will be set by backend
2601
- cyfm_size_in_byte: file.size,
2602
- cyfm_type: file.type,
2603
- cyfm_creation_dt: new Date().toISOString(),
2604
- cyfm_id_user: additionalData?.userId || '',
2605
- cyfm_permissions: additionalData?.permissions || [],
2606
- cyfm_tags: additionalData?.tags || [],
2607
- cyfm_version: 1,
2608
- cyfm_file_status_sygmt: additionalData?.fileStatus || 'active',
2609
- cyfm_isactive: true,
2610
- fileData: base64Data, // Base64 file data
2611
- ...additionalData
2612
- };
2613
- // Make the HTTP request with progress tracking
2614
- this.http.post(`${this.baseUrl}`, payload, {
2615
- reportProgress: true,
2616
- observe: 'events'
2617
- }).subscribe({
2618
- next: (event) => {
2619
- if (event.type === 1 && progressCallback) { // HttpEventType.UploadProgress
2620
- const uploadProgress = (event.loaded / event.total) * 50; // Upload is remaining 50%
2621
- const totalProgress = 50 + uploadProgress; // Total progress (50% file reading + upload progress)
2622
- progressCallback(totalProgress);
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);
2623
2725
  }
2624
- else if (event.type === 4) { // HttpEventType.Response
2625
- if (progressCallback) {
2626
- 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
+ });
2627
2738
  }
2628
- observer.next(event.body);
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
+ }
2759
+ }
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);
2629
2771
  }
2630
- },
2631
- error: (error) => observer.error(error)
2632
- });
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'));
2633
2784
  };
2634
- reader.onerror = () => observer.error('Failed to read file');
2635
2785
  reader.readAsDataURL(file);
2636
2786
  });
2637
2787
  }
2638
2788
  /**
2639
2789
  * Set the base URL for API calls
2640
- * This should be called by the consuming application during initialization
2790
+ * Angular 20: Using signal-based state management
2641
2791
  * @param url The base URL for the API
2642
2792
  */
2643
2793
  setBaseUrl(url) {
2644
2794
  console.log('🔍 [FileManagerService] Setting base URL:', url);
2645
- this.baseUrl = url;
2795
+ this._baseUrl.set(url);
2646
2796
  }
2647
- 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 });
2648
2868
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileManagerService, providedIn: 'root' });
2649
2869
  }
2650
2870
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileManagerService, decorators: [{
@@ -2652,7 +2872,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
2652
2872
  args: [{
2653
2873
  providedIn: 'root'
2654
2874
  }]
2655
- }], ctorParameters: () => [{ type: i1$1.HttpClient }] });
2875
+ }], ctorParameters: () => [] });
2656
2876
 
2657
2877
  class NotificationService {
2658
2878
  notifications = signal([], ...(ngDevMode ? [{ debugName: "notifications" }] : []));
@@ -2877,43 +3097,88 @@ class CideEleFileInputComponent {
2877
3097
  fileManagerService = inject(CideEleFileManagerService);
2878
3098
  notificationService = inject(NotificationService);
2879
3099
  elementService = inject(CideElementsService);
2880
- label = 'Choose file';
2881
- accept = '';
2882
- multiple = false;
2883
- disabled = false;
2884
- required = false;
2885
- helperText = '';
2886
- errorText = '';
2887
- showPreview = false;
2888
- previewWidth = '200px';
2889
- previewHeight = '200px';
2890
- previewBoxMode = false;
2891
- showFileName = true;
2892
- placeholderText = 'Click to select image';
2893
- placeholderIcon = '📷';
2894
- autoUpload = false;
2895
- uploadData = {};
2896
- id = Math.random().toString(36).substring(2, 10);
2897
- isUploading = false;
2898
- uploadProgress = 0;
2899
- uploadStatus = 'idle';
2900
- uploadNotificationId = null;
2901
- fileChange = new EventEmitter();
2902
- uploadSuccess = new EventEmitter();
2903
- uploadError = new EventEmitter();
2904
- uploadProgressChange = new EventEmitter();
2905
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2906
- files = null;
2907
- fileNames = [];
2908
- previewUrls = [];
2909
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3100
+ destroyRef = inject(DestroyRef);
3101
+ // private readonly pendingTasks = inject(PendingTasks); // TODO: Fix PendingTasks API usage
3102
+ // Modern input signals
3103
+ label = input('Choose file', ...(ngDevMode ? [{ debugName: "label" }] : []));
3104
+ accept = input('', ...(ngDevMode ? [{ debugName: "accept" }] : []));
3105
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
3106
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
3107
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
3108
+ helperText = input('', ...(ngDevMode ? [{ debugName: "helperText" }] : []));
3109
+ errorText = input('', ...(ngDevMode ? [{ debugName: "errorText" }] : []));
3110
+ showPreview = input(false, ...(ngDevMode ? [{ debugName: "showPreview" }] : []));
3111
+ previewWidth = input('200px', ...(ngDevMode ? [{ debugName: "previewWidth" }] : []));
3112
+ previewHeight = input('200px', ...(ngDevMode ? [{ debugName: "previewHeight" }] : []));
3113
+ previewBoxMode = input(false, ...(ngDevMode ? [{ debugName: "previewBoxMode" }] : []));
3114
+ showFileName = input(true, ...(ngDevMode ? [{ debugName: "showFileName" }] : []));
3115
+ placeholderText = input('Click to select image', ...(ngDevMode ? [{ debugName: "placeholderText" }] : []));
3116
+ placeholderIcon = input('📷', ...(ngDevMode ? [{ debugName: "placeholderIcon" }] : []));
3117
+ autoUpload = input(false, ...(ngDevMode ? [{ debugName: "autoUpload" }] : []));
3118
+ uploadData = input({}, ...(ngDevMode ? [{ debugName: "uploadData" }] : []));
3119
+ // Modern output signals
3120
+ fileChange = output();
3121
+ uploadSuccess = output();
3122
+ uploadError = output();
3123
+ uploadProgressChange = output();
3124
+ // Reactive state with signals
3125
+ id = signal(Math.random().toString(36).substring(2, 10), ...(ngDevMode ? [{ debugName: "id" }] : []));
3126
+ isUploading = signal(false, ...(ngDevMode ? [{ debugName: "isUploading" }] : []));
3127
+ uploadProgress = signal(0, ...(ngDevMode ? [{ debugName: "uploadProgress" }] : []));
3128
+ uploadStatus = signal('idle', ...(ngDevMode ? [{ debugName: "uploadStatus" }] : []));
3129
+ files = signal(null, ...(ngDevMode ? [{ debugName: "files" }] : []));
3130
+ fileNames = signal([], ...(ngDevMode ? [{ debugName: "fileNames" }] : []));
3131
+ previewUrls = signal([], ...(ngDevMode ? [{ debugName: "previewUrls" }] : []));
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" }] : []));
3153
+ // ControlValueAccessor callbacks
2910
3154
  onChange = (files) => { };
2911
3155
  onTouched = () => { };
2912
3156
  onValidatorChange = () => { };
3157
+ // Computed values
3158
+ hasImages = computed(() => this.previewUrls().length > 0, ...(ngDevMode ? [{ debugName: "hasImages" }] : []));
3159
+ isPreviewBoxMode = computed(() => this.previewBoxMode() && this.showPreview(), ...(ngDevMode ? [{ debugName: "isPreviewBoxMode" }] : []));
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
+ }
2913
3178
  writeValue(files) {
2914
3179
  console.log('📝 [FileInput] writeValue called with:', files ? Array.from(files).map(f => f.name) : 'null');
2915
- this.files = files;
2916
- this.fileNames = files ? Array.from(files).map(f => f.name) : [];
3180
+ this.files.set(files);
3181
+ this.fileNames.set(files ? Array.from(files).map(f => f.name) : []);
2917
3182
  this.generatePreviews();
2918
3183
  }
2919
3184
  registerOnChange(fn) {
@@ -2926,26 +3191,29 @@ class CideEleFileInputComponent {
2926
3191
  this.onValidatorChange = fn;
2927
3192
  }
2928
3193
  setDisabledState(isDisabled) {
2929
- this.disabled = isDisabled;
3194
+ // Note: With input signals, disabled state is controlled by the parent component
3195
+ // This method is kept for ControlValueAccessor compatibility but doesn't modify the signal
3196
+ console.log('🔧 [FileInput] setDisabledState called with:', isDisabled, '(controlled by parent component)');
2930
3197
  }
2931
3198
  onFileSelected(event) {
2932
3199
  console.log('🔍 [FileInput] onFileSelected called');
2933
3200
  const input = event.target;
2934
- this.files = input.files;
2935
- this.fileNames = this.files ? Array.from(this.files).map(f => f.name) : [];
2936
- console.log('📁 [FileInput] Files selected:', this.fileNames);
3201
+ const selectedFiles = input.files;
3202
+ this.files.set(selectedFiles);
3203
+ this.fileNames.set(selectedFiles ? Array.from(selectedFiles).map(f => f.name) : []);
3204
+ console.log('📁 [FileInput] Files selected:', this.fileNames());
2937
3205
  this.generatePreviews();
2938
3206
  // Reset upload status when new file is selected
2939
- this.uploadStatus = 'idle';
2940
- console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus);
2941
- this.onChange(this.files);
2942
- this.fileChange.emit(this.files);
3207
+ this.uploadStatus.set('idle');
3208
+ console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
3209
+ this.onChange(selectedFiles);
3210
+ this.fileChange.emit(selectedFiles);
2943
3211
  this.onTouched();
2944
3212
  this.onValidatorChange();
2945
3213
  // Auto upload if enabled
2946
- if (this.autoUpload && this.files && this.files.length > 0) {
2947
- console.log('🚀 [FileInput] Auto upload enabled, starting upload for:', this.files[0].name);
2948
- this.uploadFile(this.files[0]);
3214
+ if (this.autoUpload() && selectedFiles && selectedFiles.length > 0) {
3215
+ console.log('🚀 [FileInput] Auto upload enabled, starting upload for:', selectedFiles[0].name);
3216
+ this.uploadFile(selectedFiles[0]);
2949
3217
  }
2950
3218
  else {
2951
3219
  console.log('⏸️ [FileInput] Auto upload disabled or no files');
@@ -2953,80 +3221,107 @@ class CideEleFileInputComponent {
2953
3221
  }
2954
3222
  clearFiles() {
2955
3223
  console.log('🗑️ [FileInput] clearFiles called');
2956
- this.files = null;
2957
- this.fileNames = [];
3224
+ this.files.set(null);
3225
+ this.fileNames.set([]);
2958
3226
  this.clearPreviews();
2959
- this.uploadStatus = 'idle';
2960
- console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus);
3227
+ this.uploadStatus.set('idle');
3228
+ console.log('🔄 [FileInput] Upload status reset to:', this.uploadStatus());
2961
3229
  this.onChange(null);
2962
3230
  this.fileChange.emit(null);
2963
3231
  this.onValidatorChange();
2964
3232
  }
2965
3233
  uploadFile(file) {
2966
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');
2967
3238
  // Set upload status to 'start' before starting upload
2968
- this.uploadStatus = 'start';
2969
- console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus);
3239
+ this.uploadStatus.set('start');
3240
+ console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
2970
3241
  // Trigger validation update to show upload in progress error
2971
3242
  this.onValidatorChange();
2972
3243
  console.log('✅ [FileInput] Validation triggered - should show upload in progress error');
2973
- this.isUploading = true;
2974
- this.uploadProgress = 0;
3244
+ this.isUploading.set(true);
3245
+ this.uploadProgress.set(0);
2975
3246
  this.uploadProgressChange.emit(0);
2976
3247
  console.log('📊 [FileInput] Upload progress initialized to 0%');
2977
3248
  // Make form control invalid during upload - this prevents form submission
2978
3249
  this.onChange(null);
2979
3250
  console.log('🚫 [FileInput] Form control value set to null to prevent submission');
2980
- // Show initial progress notification
2981
- this.uploadNotificationId = this.notificationService.showProgress('Starting file upload...', 0);
2982
- console.log('🔔 [FileInput] Progress notification started with ID:', this.uploadNotificationId);
2983
- this.fileManagerService.uploadFile(file, this.uploadData, (progress) => {
3251
+ // Show initial progress notification with spinner (persistent - no auto-dismiss)
3252
+ const notificationId = this.notificationService.showProgress('🔄 Preparing file upload...', 0, { duration: 0 });
3253
+ this.uploadNotificationId.set(notificationId);
3254
+ console.log('🔔 [FileInput] Progress notification started with ID:', notificationId);
3255
+ this.fileManagerService.uploadFile(file, this.uploadData(), (progress) => {
2984
3256
  // Real progress callback from file manager service
2985
- this.uploadProgress = progress;
3257
+ this.uploadProgress.set(progress);
2986
3258
  this.uploadProgressChange.emit(progress);
2987
3259
  console.log('📈 [FileInput] Upload progress:', Math.round(progress) + '%');
2988
3260
  // Set upload status to 'uploading' when progress starts
2989
- if (this.uploadStatus === 'start') {
2990
- this.uploadStatus = 'uploading';
2991
- console.log('🔄 [FileInput] Upload status changed to:', this.uploadStatus);
3261
+ if (this.uploadStatus() === 'start') {
3262
+ this.uploadStatus.set('uploading');
3263
+ console.log('🔄 [FileInput] Upload status changed to:', this.uploadStatus());
2992
3264
  this.onValidatorChange();
2993
3265
  console.log('✅ [FileInput] Validation triggered after status change to uploading');
2994
3266
  }
2995
- // Update progress notification
2996
- if (this.uploadNotificationId) {
2997
- this.notificationService.updateProgress(this.uploadNotificationId, progress, `Uploading file... ${Math.round(progress)}%`);
3267
+ // Update progress notification with spinner
3268
+ const notificationId = this.uploadNotificationId();
3269
+ if (notificationId) {
3270
+ let progressMessage = '';
3271
+ if (progress < 10) {
3272
+ progressMessage = '🔄 Starting upload...';
3273
+ }
3274
+ else if (progress < 25) {
3275
+ progressMessage = '🔄 Uploading file...';
3276
+ }
3277
+ else if (progress < 50) {
3278
+ progressMessage = '🔄 Upload in progress...';
3279
+ }
3280
+ else if (progress < 75) {
3281
+ progressMessage = '🔄 Almost done...';
3282
+ }
3283
+ else if (progress < 95) {
3284
+ progressMessage = '🔄 Finishing upload...';
3285
+ }
3286
+ else {
3287
+ progressMessage = '🔄 Finalizing...';
3288
+ }
3289
+ this.notificationService.updateProgress(notificationId, progress, progressMessage);
2998
3290
  }
2999
- }).subscribe({
3291
+ }).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
3000
3292
  next: (response) => {
3001
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');
3002
3297
  // Set upload status to 'success'
3003
- this.uploadStatus = 'success';
3004
- console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus);
3298
+ this.uploadStatus.set('success');
3299
+ console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
3005
3300
  // Complete the progress
3006
- this.uploadProgress = 100;
3301
+ this.uploadProgress.set(100);
3007
3302
  this.uploadProgressChange.emit(100);
3008
3303
  console.log('📊 [FileInput] Upload progress completed: 100%');
3009
3304
  // Update progress notification to complete
3010
- if (this.uploadNotificationId) {
3011
- this.notificationService.remove(this.uploadNotificationId);
3305
+ const notificationId = this.uploadNotificationId();
3306
+ if (notificationId) {
3307
+ this.notificationService.remove(notificationId);
3012
3308
  console.log('🔔 [FileInput] Progress notification removed');
3013
3309
  }
3014
- this.notificationService.success('File upload completed!');
3015
- this.uploadNotificationId = null;
3016
- // Extract ID from response - the file manager returns the _id of the created record
3017
- const uploadedId = response?._id || response?.data?._id || response?.id || response?.fileId;
3310
+ this.notificationService.success('File uploaded successfully!', { duration: 0 });
3311
+ this.uploadNotificationId.set(null);
3312
+ // Extract ID from CoreFileManagerInsertUpdateResponse
3313
+ const uploadedId = response?.data?.core_file_manager?.[0]?.cyfm_id;
3018
3314
  if (uploadedId) {
3019
3315
  console.log('✅ [FileInput] File uploaded successfully with ID:', uploadedId);
3020
3316
  this.uploadSuccess.emit(uploadedId);
3021
- // Update the form control value with the uploaded file ID
3022
- this.onChange(uploadedId);
3023
- 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);
3024
3319
  }
3025
3320
  else {
3026
3321
  console.error('❌ [FileInput] Upload successful but no ID returned:', response);
3027
3322
  this.uploadError.emit('Upload successful but no ID returned');
3028
3323
  }
3029
- this.isUploading = false;
3324
+ this.isUploading.set(false);
3030
3325
  console.log('🔄 [FileInput] isUploading set to false');
3031
3326
  // Trigger validation update to clear upload in progress error
3032
3327
  this.onValidatorChange();
@@ -3034,19 +3329,23 @@ class CideEleFileInputComponent {
3034
3329
  },
3035
3330
  error: (error) => {
3036
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');
3037
3335
  // Set upload status to 'error' and remove upload validation error
3038
- this.uploadStatus = 'error';
3039
- console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus);
3336
+ this.uploadStatus.set('error');
3337
+ console.log('🔄 [FileInput] Upload status set to:', this.uploadStatus());
3040
3338
  // Remove progress notification and show error
3041
- if (this.uploadNotificationId) {
3042
- this.notificationService.remove(this.uploadNotificationId);
3339
+ const notificationId = this.uploadNotificationId();
3340
+ if (notificationId) {
3341
+ this.notificationService.remove(notificationId);
3043
3342
  console.log('🔔 [FileInput] Progress notification removed due to error');
3044
3343
  }
3045
- this.notificationService.error(`Upload failed: ${error.message || error.error?.message || 'Upload failed'}`);
3046
- this.uploadNotificationId = null;
3344
+ this.notificationService.error(`❌ File upload failed: ${error.message || error.error?.message || 'Unknown error occurred'}`, { duration: 0 });
3345
+ this.uploadNotificationId.set(null);
3047
3346
  this.uploadError.emit(error.message || error.error?.message || 'Upload failed');
3048
- this.isUploading = false;
3049
- this.uploadProgress = 0;
3347
+ this.isUploading.set(false);
3348
+ this.uploadProgress.set(0);
3050
3349
  this.uploadProgressChange.emit(0);
3051
3350
  console.log('🔄 [FileInput] Upload state reset - isUploading: false, progress: 0%');
3052
3351
  // Trigger validation update to clear upload in progress error
@@ -3058,15 +3357,15 @@ class CideEleFileInputComponent {
3058
3357
  generatePreviews() {
3059
3358
  // Clear existing previews
3060
3359
  this.clearPreviews();
3061
- if (!this.showPreview || !this.files) {
3360
+ if (!this.showPreview() || !this.files()) {
3062
3361
  return;
3063
3362
  }
3064
- Array.from(this.files).forEach(file => {
3363
+ Array.from(this.files()).forEach(file => {
3065
3364
  if (this.isImageFile(file)) {
3066
3365
  const reader = new FileReader();
3067
3366
  reader.onload = (e) => {
3068
3367
  if (e.target?.result) {
3069
- this.previewUrls.push(e.target.result);
3368
+ this.previewUrls.update(urls => [...urls, e.target.result]);
3070
3369
  }
3071
3370
  };
3072
3371
  reader.readAsDataURL(file);
@@ -3075,131 +3374,223 @@ class CideEleFileInputComponent {
3075
3374
  }
3076
3375
  clearPreviews() {
3077
3376
  // Revoke object URLs to prevent memory leaks
3078
- this.previewUrls.forEach(url => {
3377
+ this.previewUrls().forEach(url => {
3079
3378
  if (url.startsWith('blob:')) {
3080
3379
  URL.revokeObjectURL(url);
3081
3380
  }
3082
3381
  });
3083
- this.previewUrls = [];
3382
+ this.previewUrls.set([]);
3084
3383
  }
3085
3384
  isImageFile(file) {
3086
3385
  return file.type.startsWith('image/');
3087
3386
  }
3088
- isImagePreviewAvailable() {
3089
- return this.showPreview && this.previewUrls.length > 0;
3090
- }
3091
3387
  removePreview(index) {
3092
- if (this.files && this.files.length > index) {
3388
+ const currentFiles = this.files();
3389
+ if (currentFiles && currentFiles.length > index) {
3093
3390
  // Create new FileList without the removed file
3094
3391
  const dt = new DataTransfer();
3095
- Array.from(this.files).forEach((file, i) => {
3392
+ Array.from(currentFiles).forEach((file, i) => {
3096
3393
  if (i !== index) {
3097
3394
  dt.items.add(file);
3098
3395
  }
3099
3396
  });
3100
- this.files = dt.files;
3101
- this.fileNames = Array.from(this.files).map(f => f.name);
3397
+ const newFiles = dt.files;
3398
+ this.files.set(newFiles);
3399
+ this.fileNames.set(Array.from(newFiles).map(f => f.name));
3102
3400
  // Remove the preview URL
3103
- if (this.previewUrls[index] && this.previewUrls[index].startsWith('blob:')) {
3104
- URL.revokeObjectURL(this.previewUrls[index]);
3401
+ const currentUrls = this.previewUrls();
3402
+ if (currentUrls[index] && currentUrls[index].startsWith('blob:')) {
3403
+ URL.revokeObjectURL(currentUrls[index]);
3105
3404
  }
3106
- this.previewUrls.splice(index, 1);
3107
- this.onChange(this.files);
3108
- this.fileChange.emit(this.files);
3405
+ this.previewUrls.update(urls => urls.filter((_, i) => i !== index));
3406
+ this.onChange(newFiles);
3407
+ this.fileChange.emit(newFiles);
3109
3408
  }
3110
3409
  }
3111
3410
  ngOnDestroy() {
3112
3411
  // Clean up preview URLs to prevent memory leaks
3113
3412
  this.clearPreviews();
3114
3413
  // Clean up any active upload notification
3115
- if (this.uploadNotificationId) {
3116
- this.notificationService.remove(this.uploadNotificationId);
3117
- this.uploadNotificationId = null;
3414
+ const notificationId = this.uploadNotificationId();
3415
+ if (notificationId) {
3416
+ this.notificationService.remove(notificationId);
3417
+ this.uploadNotificationId.set(null);
3118
3418
  }
3119
3419
  }
3120
3420
  triggerFileSelect() {
3121
- const fileInput = document.getElementById('cide-file-input-' + this.id);
3122
- if (fileInput && !this.disabled) {
3421
+ const fileInput = document.getElementById('cide-file-input-' + this.id());
3422
+ if (fileInput && !this.disabled()) {
3123
3423
  fileInput.click();
3124
3424
  }
3125
3425
  }
3126
- isPreviewBoxMode() {
3127
- return this.previewBoxMode && this.showPreview;
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');
3128
3440
  }
3129
- hasImages() {
3130
- return this.previewUrls.length > 0;
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
+ }
3131
3518
  }
3132
3519
  isRequired() {
3133
- return this.required;
3520
+ return this.required();
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);
3134
3539
  }
3135
3540
  getCurrentState() {
3136
3541
  return {
3137
- id: this.id,
3138
- label: this.label,
3139
- required: this.required,
3140
- disabled: this.disabled,
3141
- accept: this.accept,
3142
- multiple: this.multiple,
3143
- showPreview: this.showPreview,
3144
- autoUpload: this.autoUpload,
3145
- uploadStatus: this.uploadStatus,
3146
- isUploading: this.isUploading,
3147
- uploadProgress: this.uploadProgress,
3148
- files: this.files ? Array.from(this.files).map(f => ({ name: f.name, size: f.size, type: f.type })) : null,
3149
- fileNames: this.fileNames,
3150
- previewUrls: this.previewUrls.length,
3151
- helperText: this.helperText,
3152
- errorText: this.errorText,
3153
- placeholderText: this.placeholderText,
3154
- placeholderIcon: this.placeholderIcon,
3155
- previewWidth: this.previewWidth,
3156
- previewHeight: this.previewHeight,
3157
- previewBoxMode: this.previewBoxMode,
3158
- showFileName: this.showFileName,
3159
- uploadData: this.uploadData
3542
+ id: this.id(),
3543
+ label: this.label(),
3544
+ required: this.required(),
3545
+ disabled: this.disabled(),
3546
+ accept: this.accept(),
3547
+ multiple: this.multiple(),
3548
+ showPreview: this.showPreview(),
3549
+ autoUpload: this.autoUpload(),
3550
+ uploadStatus: this.uploadStatus(),
3551
+ isUploading: this.isUploading(),
3552
+ uploadProgress: this.uploadProgress(),
3553
+ files: this.files() ? Array.from(this.files()).map(f => ({ name: f.name, size: f.size, type: f.type })) : null,
3554
+ fileNames: this.fileNames(),
3555
+ previewUrls: this.previewUrls().length,
3556
+ helperText: this.helperText(),
3557
+ errorText: this.errorText(),
3558
+ placeholderText: this.placeholderText(),
3559
+ placeholderIcon: this.placeholderIcon(),
3560
+ previewWidth: this.previewWidth(),
3561
+ previewHeight: this.previewHeight(),
3562
+ previewBoxMode: this.previewBoxMode(),
3563
+ showFileName: this.showFileName(),
3564
+ uploadData: this.uploadData()
3160
3565
  };
3161
3566
  }
3162
3567
  async getControlData() {
3163
3568
  console.log('🔍 [FileInput] getControlData called');
3164
- const cide_element_data = await this.elementService?.getElementData({ sype_key: this.id });
3569
+ const cide_element_data = await this.elementService?.getElementData({ sype_key: this.id() });
3165
3570
  if (cide_element_data) {
3166
3571
  console.log('📋 [FileInput] Element data loaded:', cide_element_data);
3167
- // Update basic properties that exist in ICoreSype
3168
- this.label = cide_element_data?.sype_label;
3169
- this.required = (cide_element_data?.sype_required || false);
3170
- this.placeholderText = (cide_element_data?.sype_placeholder || 'Click to select image');
3171
- // Update file-input specific properties from data object if available
3172
- const data = cide_element_data?.data;
3173
- if (data) {
3174
- this.accept = data.accept || '';
3175
- this.multiple = data.multiple || false;
3176
- this.showPreview = data.showPreview || false;
3177
- this.autoUpload = data.autoUpload || false;
3178
- this.placeholderIcon = data.placeholderIcon || '📷';
3179
- this.previewWidth = data.previewWidth || '200px';
3180
- this.previewHeight = data.previewHeight || '200px';
3181
- this.previewBoxMode = data.previewBoxMode || false;
3182
- this.showFileName = data.showFileName || true;
3183
- this.uploadData = data.uploadData || {};
3184
- }
3185
- console.log('✅ [FileInput] Control data updated from element service');
3572
+ // Note: Since we're using input signals, we can't directly set their values
3573
+ // This method would need to be refactored to work with the new signal-based approach
3574
+ // For now, we'll log the data and trigger validation
3575
+ console.log('✅ [FileInput] Control data received from element service');
3576
+ console.log('⚠️ [FileInput] Note: Input signals cannot be modified after component initialization');
3186
3577
  // Trigger validation update
3187
3578
  this.onValidatorChange();
3188
3579
  }
3189
3580
  else {
3190
- console.log('⚠️ [FileInput] No element data found for key:', this.id);
3581
+ console.log('⚠️ [FileInput] No element data found for key:', this.id());
3191
3582
  }
3192
3583
  }
3193
3584
  // Validator implementation
3194
3585
  validate(control) {
3195
- console.log('🔍 [FileInput] validate() called - uploadStatus:', this.uploadStatus, 'required:', this.required, 'files:', !!this.files, 'control.value:', control.value);
3586
+ console.log('🔍 [FileInput] validate() called - uploadStatus:', this.uploadStatus(), 'required:', this.required(), 'files:', !!this.files(), 'control.value:', control.value);
3196
3587
  // If upload is in progress (start or uploading status), return validation error
3197
- if (this.uploadStatus === 'start' || this.uploadStatus === 'uploading') {
3588
+ if (this.uploadStatus() === 'start' || this.uploadStatus() === 'uploading') {
3198
3589
  console.log('⚠️ [FileInput] Validation ERROR: Upload in progress');
3199
3590
  return { 'uploadInProgress': { message: 'File upload in progress. Please wait...' } };
3200
3591
  }
3201
3592
  // If required and no file is selected and no control value (uploaded file ID), return validation error
3202
- if (this.required && !this.files && !control.value) {
3593
+ if (this.required() && !this.files() && !control.value) {
3203
3594
  console.log('⚠️ [FileInput] Validation ERROR: File required');
3204
3595
  return { 'required': { message: 'Please select a file to upload.' } };
3205
3596
  }
@@ -3207,74 +3598,34 @@ class CideEleFileInputComponent {
3207
3598
  return null; // No validation errors
3208
3599
  }
3209
3600
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3210
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: CideEleFileInputComponent, isStandalone: true, selector: "cide-ele-file-input", inputs: { label: "label", accept: "accept", multiple: "multiple", disabled: "disabled", required: "required", helperText: "helperText", errorText: "errorText", showPreview: "showPreview", previewWidth: "previewWidth", previewHeight: "previewHeight", previewBoxMode: "previewBoxMode", showFileName: "showFileName", placeholderText: "placeholderText", placeholderIcon: "placeholderIcon", autoUpload: "autoUpload", uploadData: "uploadData" }, outputs: { fileChange: "fileChange", uploadSuccess: "uploadSuccess", uploadError: "uploadError", uploadProgressChange: "uploadProgressChange" }, providers: [
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: [
3211
3602
  {
3212
3603
  provide: NG_VALUE_ACCESSOR,
3213
- useExisting: forwardRef(() => CideEleFileInputComponent),
3604
+ useExisting: CideEleFileInputComponent,
3214
3605
  multi: true
3215
3606
  },
3216
3607
  {
3217
3608
  provide: NG_VALIDATORS,
3218
- useExisting: forwardRef(() => CideEleFileInputComponent),
3609
+ useExisting: CideEleFileInputComponent,
3219
3610
  multi: true
3220
3611
  }
3221
- ], 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"] }] });
3222
3613
  }
3223
3614
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, decorators: [{
3224
3615
  type: Component,
3225
3616
  args: [{ selector: 'cide-ele-file-input', standalone: true, imports: [CommonModule, FormsModule, CideIconComponent], providers: [
3226
3617
  {
3227
3618
  provide: NG_VALUE_ACCESSOR,
3228
- useExisting: forwardRef(() => CideEleFileInputComponent),
3619
+ useExisting: CideEleFileInputComponent,
3229
3620
  multi: true
3230
3621
  },
3231
3622
  {
3232
3623
  provide: NG_VALIDATORS,
3233
- useExisting: forwardRef(() => CideEleFileInputComponent),
3624
+ useExisting: CideEleFileInputComponent,
3234
3625
  multi: true
3235
3626
  }
3236
- ], 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"] }]
3237
- }], propDecorators: { label: [{
3238
- type: Input
3239
- }], accept: [{
3240
- type: Input
3241
- }], multiple: [{
3242
- type: Input
3243
- }], disabled: [{
3244
- type: Input
3245
- }], required: [{
3246
- type: Input
3247
- }], helperText: [{
3248
- type: Input
3249
- }], errorText: [{
3250
- type: Input
3251
- }], showPreview: [{
3252
- type: Input
3253
- }], previewWidth: [{
3254
- type: Input
3255
- }], previewHeight: [{
3256
- type: Input
3257
- }], previewBoxMode: [{
3258
- type: Input
3259
- }], showFileName: [{
3260
- type: Input
3261
- }], placeholderText: [{
3262
- type: Input
3263
- }], placeholderIcon: [{
3264
- type: Input
3265
- }], autoUpload: [{
3266
- type: Input
3267
- }], uploadData: [{
3268
- type: Input
3269
- }], fileChange: [{
3270
- type: Output
3271
- }], uploadSuccess: [{
3272
- type: Output
3273
- }], uploadError: [{
3274
- type: Output
3275
- }], uploadProgressChange: [{
3276
- type: Output
3277
- }] } });
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: () => [] });
3278
3629
 
3279
3630
  class CideTextareaComponent {
3280
3631
  label = '';