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