cloud-ide-element 1.0.66 → 1.0.68

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.
@@ -2598,6 +2598,7 @@ class ICoreCyfmSave {
2598
2598
  cyfm_tags;
2599
2599
  cyfm_version;
2600
2600
  cyfm_file_status_sygmt;
2601
+ cyfm_group_id; // Group ID for multiple file uploads
2601
2602
  cyfm_isactive;
2602
2603
  cyfm_file_base64 = "";
2603
2604
  cyfm_temp_unique_id = "";
@@ -2625,12 +2626,14 @@ class CideEleFileManagerService {
2625
2626
  destroyRef = inject(DestroyRef);
2626
2627
  // Angular 20: Signal-based state management
2627
2628
  _baseUrl = signal('/api', ...(ngDevMode ? [{ debugName: "_baseUrl" }] : []));
2629
+ _userId = signal('', ...(ngDevMode ? [{ debugName: "_userId" }] : []));
2628
2630
  _isUploading = signal(false, ...(ngDevMode ? [{ debugName: "_isUploading" }] : []));
2629
2631
  _uploadQueue = signal([], ...(ngDevMode ? [{ debugName: "_uploadQueue" }] : []));
2630
2632
  _activeUploads = signal(new Map(), ...(ngDevMode ? [{ debugName: "_activeUploads" }] : []));
2631
2633
  _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
2632
2634
  // Angular 20: Computed values
2633
2635
  baseUrl = this._baseUrl.asReadonly();
2636
+ userId = this._userId.asReadonly();
2634
2637
  isUploading = this._isUploading.asReadonly();
2635
2638
  uploadQueue = this._uploadQueue.asReadonly();
2636
2639
  activeUploads = this._activeUploads.asReadonly();
@@ -2699,11 +2702,12 @@ class CideEleFileManagerService {
2699
2702
  cyfm_size_in_byte: file.size,
2700
2703
  cyfm_type: file.type,
2701
2704
  cyfm_creation_dt: new Date().toISOString(),
2702
- cyfm_id_user: uploadOptions?.userId || '',
2705
+ cyfm_id_user: uploadOptions?.userId || this._userId() || '',
2703
2706
  cyfm_permissions: [], // Match the interface type
2704
2707
  cyfm_tags: uploadOptions?.tags || [],
2705
2708
  cyfm_version: 1,
2706
2709
  cyfm_file_status_sygmt: uploadOptions?.fileStatus || 'file_manager_file_status_active',
2710
+ cyfm_group_id: uploadOptions?.groupId, // Group ID for multiple file uploads
2707
2711
  cyfm_isactive: true,
2708
2712
  cyfm_file_base64: base64Data,
2709
2713
  cyfm_temp_unique_id: fileId
@@ -2796,6 +2800,29 @@ class CideEleFileManagerService {
2796
2800
  console.log('🔍 [FileManagerService] Setting base URL:', url);
2797
2801
  this._baseUrl.set(url);
2798
2802
  }
2803
+ /**
2804
+ * Set the user ID for file uploads
2805
+ * Angular 20: Using signal-based state management
2806
+ * @param userId The user ID to associate with uploaded files
2807
+ */
2808
+ setUserId(userId) {
2809
+ console.log('🔍 [FileManagerService] Setting user ID:', userId);
2810
+ this._userId.set(userId);
2811
+ }
2812
+ /**
2813
+ * Generate Object ID for group uploads
2814
+ * Calls the backend API to generate a unique ObjectId for grouping multiple files
2815
+ * @returns Observable with the generated ObjectId
2816
+ */
2817
+ generateObjectId() {
2818
+ const url = `${this._baseUrl()}/utility/generateObjectId`;
2819
+ console.log('🆔 [FileManagerService] Generating ObjectId from:', url);
2820
+ return this.http.get(url).pipe(catchError((error) => {
2821
+ console.error('❌ [FileManagerService] Error generating ObjectId:', error);
2822
+ this._error.set(`Failed to generate ObjectId: ${error.message}`);
2823
+ return throwError(() => error);
2824
+ }), takeUntilDestroyed(this.destroyRef));
2825
+ }
2799
2826
  /**
2800
2827
  * Angular 20: Helper methods for state management
2801
2828
  */
@@ -3127,6 +3154,7 @@ class CideEleFileInputComponent {
3127
3154
  placeholderIcon = '📷';
3128
3155
  autoUpload = false;
3129
3156
  uploadData = {};
3157
+ multipleFileUpload = false; // New property for multiple file upload mode
3130
3158
  // Traditional @Output() decorators
3131
3159
  fileChange = new EventEmitter();
3132
3160
  uploadSuccess = new EventEmitter();
@@ -3149,6 +3177,7 @@ class CideEleFileInputComponent {
3149
3177
  placeholderIconSignal = signal(this.placeholderIcon, ...(ngDevMode ? [{ debugName: "placeholderIconSignal" }] : []));
3150
3178
  autoUploadSignal = signal(this.autoUpload, ...(ngDevMode ? [{ debugName: "autoUploadSignal" }] : []));
3151
3179
  uploadDataSignal = signal(this.uploadData, ...(ngDevMode ? [{ debugName: "uploadDataSignal" }] : []));
3180
+ multipleFileUploadSignal = signal(this.multipleFileUpload, ...(ngDevMode ? [{ debugName: "multipleFileUploadSignal" }] : []));
3152
3181
  // Reactive state with signals
3153
3182
  id = signal(Math.random().toString(36).substring(2, 10), ...(ngDevMode ? [{ debugName: "id" }] : []));
3154
3183
  isUploading = signal(false, ...(ngDevMode ? [{ debugName: "isUploading" }] : []));
@@ -3159,6 +3188,7 @@ class CideEleFileInputComponent {
3159
3188
  previewUrls = signal([], ...(ngDevMode ? [{ debugName: "previewUrls" }] : []));
3160
3189
  uploadNotificationId = signal(null, ...(ngDevMode ? [{ debugName: "uploadNotificationId" }] : []));
3161
3190
  isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
3191
+ groupId = signal(null, ...(ngDevMode ? [{ debugName: "groupId" }] : [])); // Group ID for multiple file uploads
3162
3192
  // Computed signals for better relationships
3163
3193
  hasFiles = computed(() => this.files() !== null && this.files().length > 0, ...(ngDevMode ? [{ debugName: "hasFiles" }] : []));
3164
3194
  canUpload = computed(() => this.hasFiles() && !this.isUploading() && !this.disabledSignal(), ...(ngDevMode ? [{ debugName: "canUpload" }] : []));
@@ -3221,6 +3251,44 @@ class CideEleFileInputComponent {
3221
3251
  this.placeholderIconSignal.set(this.placeholderIcon);
3222
3252
  this.autoUploadSignal.set(this.autoUpload);
3223
3253
  this.uploadDataSignal.set(this.uploadData);
3254
+ this.multipleFileUploadSignal.set(this.multipleFileUpload);
3255
+ }
3256
+ ngOnChanges(changes) {
3257
+ // Angular 20: Update signals when @Input() values change
3258
+ if (changes['label'])
3259
+ this.labelSignal.set(this.label);
3260
+ if (changes['accept'])
3261
+ this.acceptSignal.set(this.accept);
3262
+ if (changes['multiple'])
3263
+ this.multipleSignal.set(this.multiple);
3264
+ if (changes['disabled'])
3265
+ this.disabledSignal.set(this.disabled);
3266
+ if (changes['required'])
3267
+ this.requiredSignal.set(this.required);
3268
+ if (changes['helperText'])
3269
+ this.helperTextSignal.set(this.helperText);
3270
+ if (changes['errorText'])
3271
+ this.errorTextSignal.set(this.errorText);
3272
+ if (changes['showPreview'])
3273
+ this.showPreviewSignal.set(this.showPreview);
3274
+ if (changes['previewWidth'])
3275
+ this.previewWidthSignal.set(this.previewWidth);
3276
+ if (changes['previewHeight'])
3277
+ this.previewHeightSignal.set(this.previewHeight);
3278
+ if (changes['previewBoxMode'])
3279
+ this.previewBoxModeSignal.set(this.previewBoxMode);
3280
+ if (changes['showFileName'])
3281
+ this.showFileNameSignal.set(this.showFileName);
3282
+ if (changes['placeholderText'])
3283
+ this.placeholderTextSignal.set(this.placeholderText);
3284
+ if (changes['placeholderIcon'])
3285
+ this.placeholderIconSignal.set(this.placeholderIcon);
3286
+ if (changes['autoUpload'])
3287
+ this.autoUploadSignal.set(this.autoUpload);
3288
+ if (changes['uploadData'])
3289
+ this.uploadDataSignal.set(this.uploadData);
3290
+ if (changes['multipleFileUpload'])
3291
+ this.multipleFileUploadSignal.set(this.multipleFileUpload);
3224
3292
  }
3225
3293
  writeValue(value) {
3226
3294
  console.log('📝 [FileInput] writeValue called with:', value);
@@ -3278,8 +3346,14 @@ class CideEleFileInputComponent {
3278
3346
  this.onTouched();
3279
3347
  // Auto upload if enabled
3280
3348
  if (this.autoUploadSignal() && selectedFiles && selectedFiles.length > 0) {
3281
- console.log('🚀 [FileInput] Auto upload enabled, starting upload for:', selectedFiles[0].name);
3282
- this.uploadFile(selectedFiles[0]);
3349
+ if (this.multipleFileUploadSignal() && this.multipleSignal()) {
3350
+ console.log('🚀 [FileInput] Auto upload enabled for multiple files:', selectedFiles.length);
3351
+ this.uploadMultipleFiles(Array.from(selectedFiles));
3352
+ }
3353
+ else {
3354
+ console.log('🚀 [FileInput] Auto upload enabled, starting upload for:', selectedFiles[0].name);
3355
+ this.uploadFile(selectedFiles[0]);
3356
+ }
3283
3357
  }
3284
3358
  else {
3285
3359
  console.log('⏸️ [FileInput] Auto upload disabled or no files');
@@ -3410,6 +3484,136 @@ class CideEleFileInputComponent {
3410
3484
  }
3411
3485
  });
3412
3486
  }
3487
+ /**
3488
+ * Upload multiple files with group ID support
3489
+ * Generates a group ID if not provided and uploads all files with the same group ID
3490
+ */
3491
+ uploadMultipleFiles(files) {
3492
+ console.log('📤 [FileInput] uploadMultipleFiles called for:', files.length, 'files');
3493
+ // Set upload status to 'start' before starting upload
3494
+ this.uploadStatus.set('start');
3495
+ this.isUploading.set(true);
3496
+ this.uploadProgress.set(0);
3497
+ this.uploadProgressChange.emit(0);
3498
+ // Make form control invalid during upload
3499
+ this.onChange(null);
3500
+ // Show initial progress notification
3501
+ const notificationId = this.notificationService.showProgress('🔄 Preparing multiple file upload...', 0, { duration: 0 });
3502
+ this.uploadNotificationId.set(notificationId);
3503
+ // Check if we already have a group ID from uploadData
3504
+ const existingGroupId = this.uploadDataSignal().groupId;
3505
+ if (existingGroupId) {
3506
+ console.log('🆔 [FileInput] Using existing group ID:', existingGroupId);
3507
+ this.groupId.set(existingGroupId);
3508
+ this.startMultipleFileUpload(files, existingGroupId);
3509
+ }
3510
+ else {
3511
+ console.log('🆔 [FileInput] No group ID provided, generating new one...');
3512
+ // Generate group ID first
3513
+ this.fileManagerService.generateObjectId().subscribe({
3514
+ next: (response) => {
3515
+ const newGroupId = response.objectId;
3516
+ console.log('🆔 [FileInput] Generated new group ID:', newGroupId);
3517
+ this.groupId.set(newGroupId);
3518
+ this.startMultipleFileUpload(files, newGroupId);
3519
+ },
3520
+ error: (error) => {
3521
+ console.error('❌ [FileInput] Failed to generate group ID:', error);
3522
+ this.uploadError.emit('Failed to generate group ID');
3523
+ this.isUploading.set(false);
3524
+ this.uploadStatus.set('error');
3525
+ const notificationId = this.uploadNotificationId();
3526
+ if (notificationId) {
3527
+ this.notificationService.remove(notificationId);
3528
+ }
3529
+ this.notificationService.error('❌ Failed to generate group ID for multiple file upload', { duration: 0 });
3530
+ this.uploadNotificationId.set(null);
3531
+ }
3532
+ });
3533
+ }
3534
+ }
3535
+ /**
3536
+ * Start uploading multiple files with the provided group ID
3537
+ */
3538
+ startMultipleFileUpload(files, groupId) {
3539
+ console.log('🚀 [FileInput] Starting upload for', files.length, 'files with group ID:', groupId);
3540
+ let completedUploads = 0;
3541
+ let failedUploads = 0;
3542
+ const totalFiles = files.length;
3543
+ // Update upload data with group ID
3544
+ const uploadDataWithGroupId = {
3545
+ ...this.uploadDataSignal(),
3546
+ groupId: groupId
3547
+ };
3548
+ files.forEach((file, index) => {
3549
+ this.fileManagerService.uploadFile(file, uploadDataWithGroupId, (progress) => {
3550
+ // Calculate overall progress
3551
+ const fileProgress = progress / totalFiles;
3552
+ const overallProgress = ((completedUploads * 100) + fileProgress) / totalFiles;
3553
+ this.uploadProgress.set(overallProgress);
3554
+ this.uploadProgressChange.emit(overallProgress);
3555
+ // Update progress notification
3556
+ const notificationId = this.uploadNotificationId();
3557
+ if (notificationId) {
3558
+ const progressMessage = `🔄 Uploading file ${index + 1} of ${totalFiles}...`;
3559
+ this.notificationService.updateProgress(notificationId, overallProgress, progressMessage);
3560
+ }
3561
+ }).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
3562
+ next: (response) => {
3563
+ completedUploads++;
3564
+ console.log(`✅ [FileInput] File ${index + 1}/${totalFiles} uploaded successfully`);
3565
+ // Check if all files are completed
3566
+ if (completedUploads + failedUploads === totalFiles) {
3567
+ this.handleMultipleUploadComplete(completedUploads, failedUploads, totalFiles, groupId);
3568
+ }
3569
+ },
3570
+ error: (error) => {
3571
+ failedUploads++;
3572
+ console.error(`❌ [FileInput] File ${index + 1}/${totalFiles} upload failed:`, error);
3573
+ // Check if all files are completed
3574
+ if (completedUploads + failedUploads === totalFiles) {
3575
+ this.handleMultipleUploadComplete(completedUploads, failedUploads, totalFiles, groupId);
3576
+ }
3577
+ }
3578
+ });
3579
+ });
3580
+ }
3581
+ /**
3582
+ * Handle completion of multiple file upload
3583
+ */
3584
+ handleMultipleUploadComplete(completed, failed, total, groupId) {
3585
+ console.log(`📊 [FileInput] Multiple upload complete: ${completed}/${total} successful, ${failed} failed`);
3586
+ this.isUploading.set(false);
3587
+ this.uploadProgress.set(100);
3588
+ this.uploadProgressChange.emit(100);
3589
+ // Remove progress notification
3590
+ const notificationId = this.uploadNotificationId();
3591
+ if (notificationId) {
3592
+ this.notificationService.remove(notificationId);
3593
+ }
3594
+ this.uploadNotificationId.set(null);
3595
+ if (failed === 0) {
3596
+ // All files uploaded successfully
3597
+ this.uploadStatus.set('success');
3598
+ this.notificationService.success(`✅ All ${total} files uploaded successfully!`, { duration: 0 });
3599
+ // For multiple file upload, return the group ID as the form value
3600
+ this.onChange(groupId);
3601
+ this.uploadSuccess.emit(groupId);
3602
+ console.log('📝 [FileInput] Form control value set to group ID:', groupId);
3603
+ }
3604
+ else if (completed > 0) {
3605
+ // Some files uploaded successfully
3606
+ this.uploadStatus.set('error');
3607
+ this.notificationService.warning(`⚠️ ${completed}/${total} files uploaded. ${failed} failed.`, { duration: 0 });
3608
+ this.uploadError.emit(`${failed} out of ${total} files failed to upload`);
3609
+ }
3610
+ else {
3611
+ // All files failed
3612
+ this.uploadStatus.set('error');
3613
+ this.notificationService.error(`❌ All ${total} files failed to upload.`, { duration: 0 });
3614
+ this.uploadError.emit('All files failed to upload');
3615
+ }
3616
+ }
3413
3617
  generatePreviews() {
3414
3618
  // Clear existing previews
3415
3619
  this.clearPreviews();
@@ -3717,7 +3921,7 @@ class CideEleFileInputComponent {
3717
3921
  return null; // No validation errors
3718
3922
  }
3719
3923
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3720
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.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: [
3924
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.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", multipleFileUpload: "multipleFileUpload" }, outputs: { fileChange: "fileChange", uploadSuccess: "uploadSuccess", uploadError: "uploadError", uploadProgressChange: "uploadProgressChange" }, providers: [
3721
3925
  {
3722
3926
  provide: NG_VALUE_ACCESSOR,
3723
3927
  useExisting: CideEleFileInputComponent,
@@ -3728,7 +3932,7 @@ class CideEleFileInputComponent {
3728
3932
  useExisting: CideEleFileInputComponent,
3729
3933
  multi: true
3730
3934
  }
3731
- ], 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 (labelSignal() && !isPreviewBoxMode()) {\n <label class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ labelSignal() }}@if (requiredSignal()) {<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]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\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]=\"disabledSignal()\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [class.cide-file-input-preview-box-drag-over]=\"isDragOver()\"\n [style.width]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\"\n (click)=\"triggerFileSelect()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [attr.title]=\"disabledSignal() ? 'File selection disabled' : placeholderTextSignal()\">\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' : placeholderIconSignal() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">\n {{ isDragOver() ? 'Drop files here...' : placeholderTextSignal() }}\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 (!disabledSignal()) {\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 && showFileNameSignal()) {\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]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\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]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\">\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 (errorTextSignal()) {\n <div class=\"cide-file-input-error\">{{ errorTextSignal() }}</div>\n }\n @if (helperTextSignal() && !errorTextSignal()) {\n <div class=\"cide-file-input-helper\">{{ helperTextSignal() }}</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"] }] });
3935
+ ], usesOnChanges: true, 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 (labelSignal() && !isPreviewBoxMode()) {\n <label class=\"cide-file-input-label\" [attr.for]=\"'cide-file-input-' + id()\">\n {{ labelSignal() }}@if (requiredSignal()) {<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]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\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]=\"disabledSignal()\"\n [class.cide-file-input-preview-box-has-image]=\"hasImages()\"\n [class.cide-file-input-preview-box-drag-over]=\"isDragOver()\"\n [style.width]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\"\n (click)=\"triggerFileSelect()\"\n (dragover)=\"onDragOver($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [attr.title]=\"disabledSignal() ? 'File selection disabled' : placeholderTextSignal()\">\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' : placeholderIconSignal() }}</cide-ele-icon>\n </div>\n <div class=\"cide-file-input-preview-box-text\">\n {{ isDragOver() ? 'Drop files here...' : placeholderTextSignal() }}\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 (!disabledSignal()) {\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 && showFileNameSignal()) {\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]=\"acceptSignal()\"\n [attr.multiple]=\"multipleSignal() ? true : null\"\n [disabled]=\"disabledSignal()\"\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]=\"previewWidthSignal()\"\n [style.height]=\"previewHeightSignal()\">\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 (errorTextSignal()) {\n <div class=\"cide-file-input-error\">{{ errorTextSignal() }}</div>\n }\n @if (helperTextSignal() && !errorTextSignal()) {\n <div class=\"cide-file-input-helper\">{{ helperTextSignal() }}</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"] }] });
3732
3936
  }
3733
3937
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleFileInputComponent, decorators: [{
3734
3938
  type: Component,
@@ -3776,6 +3980,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
3776
3980
  type: Input
3777
3981
  }], uploadData: [{
3778
3982
  type: Input
3983
+ }], multipleFileUpload: [{
3984
+ type: Input
3779
3985
  }], fileChange: [{
3780
3986
  type: Output
3781
3987
  }], uploadSuccess: [{
@@ -4714,8 +4920,14 @@ class CideEleDataGridComponent {
4714
4920
  let processedData = data;
4715
4921
  // Transform to tree structure if tree config is enabled
4716
4922
  if (this.mergedConfig().tree?.enabled) {
4923
+ // Preserve expansion state from current data before transforming
4924
+ const currentData = this.internalData();
4925
+ const expansionState = this.preserveTreeExpansionState(currentData);
4717
4926
  processedData = this.transformToTree(data);
4718
4927
  console.log('processedData', processedData);
4928
+ // Apply preserved expansion state to new tree data
4929
+ processedData = this.applyTreeExpansionState(processedData, expansionState);
4930
+ console.log('processedData with preserved expansion state', processedData);
4719
4931
  }
4720
4932
  this.internalData.set(processedData);
4721
4933
  console.log('processedData', processedData);
@@ -4747,7 +4959,12 @@ class CideEleDataGridComponent {
4747
4959
  let data = this.config?.data || [];
4748
4960
  // Transform to tree structure if tree config is enabled
4749
4961
  if (this.mergedConfig().tree?.enabled) {
4962
+ // Preserve expansion state from current data before transforming
4963
+ const currentData = this.internalData();
4964
+ const expansionState = this.preserveTreeExpansionState(currentData);
4750
4965
  data = this.transformToTree(data);
4966
+ // Apply preserved expansion state to new tree data
4967
+ data = this.applyTreeExpansionState(data, expansionState);
4751
4968
  }
4752
4969
  this.internalData.set(data);
4753
4970
  // Set pagination values based on mode
@@ -4800,11 +5017,13 @@ class CideEleDataGridComponent {
4800
5017
  if (this.isDragDropEnabled()) {
4801
5018
  // Get the full dataset from the config (not just displayedData)
4802
5019
  const fullData = this.mergedConfig().data || [];
5020
+ // Flatten the data to include all items (parents and children) for hierarchical structures
5021
+ const flattenedData = this.flattenAllDataForOrderTracking(fullData);
4803
5022
  // Completely build the order tracking from current data
4804
5023
  this.rowOrderMap.clear();
4805
5024
  this.initialDataOrderIds = [];
4806
- // Build the order tracking with current data
4807
- fullData.forEach((item, index) => {
5025
+ // Build the order tracking with flattened data
5026
+ flattenedData.forEach((item, index) => {
4808
5027
  const itemId = this.getItemId(item);
4809
5028
  if (itemId) {
4810
5029
  const actualOrder = this.getItemOrder(item);
@@ -4817,7 +5036,7 @@ class CideEleDataGridComponent {
4817
5036
  });
4818
5037
  // Clear any existing local reordered data
4819
5038
  this.localReorderedData = [];
4820
- console.log('🔍 Initial order tracking built from full dataset:', this.initialDataOrderIds.length, 'items');
5039
+ console.log('🔍 Initial order tracking built from flattened dataset:', this.initialDataOrderIds.length, 'items (including children)');
4821
5040
  console.log('🔍 Initial order map created with actual order values:', Array.from(this.rowOrderMap.entries()));
4822
5041
  }
4823
5042
  }
@@ -4828,11 +5047,13 @@ class CideEleDataGridComponent {
4828
5047
  if (this.isDragDropEnabled()) {
4829
5048
  // Get the current full dataset
4830
5049
  const fullData = this.mergedConfig().data || [];
5050
+ // Flatten the data to include all items (parents and children) for hierarchical structures
5051
+ const flattenedData = this.flattenAllDataForOrderTracking(fullData);
4831
5052
  // Completely rebuild the order tracking from current data
4832
5053
  this.rowOrderMap.clear();
4833
5054
  this.initialDataOrderIds = [];
4834
- // Rebuild the order tracking with current data
4835
- fullData.forEach((item, index) => {
5055
+ // Rebuild the order tracking with flattened data
5056
+ flattenedData.forEach((item, index) => {
4836
5057
  const itemId = this.getItemId(item);
4837
5058
  if (itemId) {
4838
5059
  const actualOrder = this.getItemOrder(item);
@@ -4878,6 +5099,59 @@ class CideEleDataGridComponent {
4878
5099
  }
4879
5100
  this.updatePaginationState();
4880
5101
  }
5102
+ /**
5103
+ * Preserve tree expansion state from current data
5104
+ */
5105
+ preserveTreeExpansionState(currentData) {
5106
+ const expansionState = new Map();
5107
+ const treeConfig = this.mergedConfig().tree;
5108
+ if (!treeConfig)
5109
+ return expansionState;
5110
+ const { expandedKey = 'isExpanded', primaryKey } = treeConfig;
5111
+ const extractExpansionState = (items) => {
5112
+ items.forEach(item => {
5113
+ const itemId = String(this.getNestedValue(item, primaryKey) || '');
5114
+ if (itemId) {
5115
+ const isExpanded = this.getNestedValue(item, expandedKey) || false;
5116
+ expansionState.set(itemId, isExpanded);
5117
+ }
5118
+ // Check for children recursively
5119
+ const children = this.getNestedValue(item, treeConfig.childrenKey || 'children') || [];
5120
+ if (children.length > 0) {
5121
+ extractExpansionState(children);
5122
+ }
5123
+ });
5124
+ };
5125
+ extractExpansionState(currentData);
5126
+ console.log('🔍 DataGrid: Preserved expansion state for', expansionState.size, 'items');
5127
+ return expansionState;
5128
+ }
5129
+ /**
5130
+ * Apply preserved tree expansion state to new data
5131
+ */
5132
+ applyTreeExpansionState(data, expansionState) {
5133
+ const treeConfig = this.mergedConfig().tree;
5134
+ if (!treeConfig)
5135
+ return data;
5136
+ const { expandedKey = 'isExpanded', primaryKey, childrenKey = 'children' } = treeConfig;
5137
+ const applyToItems = (items) => {
5138
+ return items.map(item => {
5139
+ const itemId = String(this.getNestedValue(item, primaryKey) || '');
5140
+ const isExpanded = expansionState.get(itemId) || false;
5141
+ const updatedItem = {
5142
+ ...item,
5143
+ [expandedKey]: isExpanded
5144
+ };
5145
+ // Apply to children recursively
5146
+ const children = this.getNestedValue(item, childrenKey) || [];
5147
+ if (children.length > 0) {
5148
+ this.setNestedValue(updatedItem, childrenKey, applyToItems(children));
5149
+ }
5150
+ return updatedItem;
5151
+ });
5152
+ };
5153
+ return applyToItems(data);
5154
+ }
4881
5155
  /**
4882
5156
  * Transform flat data to tree structure based on foreign key relationships
4883
5157
  */
@@ -5599,15 +5873,18 @@ class CideEleDataGridComponent {
5599
5873
  }
5600
5874
  /**
5601
5875
  * Reset the row order map to original positions (for reset action)
5876
+ * Includes all items from hierarchical structure (parents and children)
5602
5877
  */
5603
5878
  resetRowOrderMap() {
5604
5879
  // Get the current full dataset from the config (not cached data)
5605
5880
  const fullData = this.mergedConfig().data || [];
5881
+ // Flatten the data to include all items (parents and children) for hierarchical structures
5882
+ const flattenedData = this.flattenAllDataForOrderTracking(fullData);
5606
5883
  // Completely rebuild the order tracking from current data
5607
5884
  this.rowOrderMap.clear();
5608
5885
  this.initialDataOrderIds = [];
5609
- // Rebuild the order tracking with current data
5610
- fullData.forEach((item, index) => {
5886
+ // Rebuild the order tracking with flattened data
5887
+ flattenedData.forEach((item, index) => {
5611
5888
  const itemId = this.getItemId(item);
5612
5889
  if (itemId) {
5613
5890
  const actualOrder = this.getItemOrder(item);
@@ -5621,19 +5898,22 @@ class CideEleDataGridComponent {
5621
5898
  // Clear any local reordered data
5622
5899
  this.localReorderedData = [];
5623
5900
  this.hasOrderChanged.set(false);
5624
- console.log('🔍 Row order map completely rebuilt from current data:', this.initialDataOrderIds.length, 'items');
5901
+ console.log('🔍 Row order map completely rebuilt from flattened data:', this.initialDataOrderIds.length, 'items (including children)');
5625
5902
  }
5626
5903
  /**
5627
5904
  * Update the row order map baseline to current positions (for save action)
5905
+ * Includes all items from hierarchical structure (parents and children)
5628
5906
  */
5629
5907
  updateRowOrderMapBaseline() {
5630
5908
  // Get the current full dataset from the config
5631
5909
  const fullData = this.mergedConfig().data || [];
5910
+ // Flatten the data to include all items (parents and children) for hierarchical structures
5911
+ const flattenedData = this.flattenAllDataForOrderTracking(fullData);
5632
5912
  // Completely rebuild the order tracking from current data
5633
5913
  this.rowOrderMap.clear();
5634
5914
  this.initialDataOrderIds = [];
5635
- // Rebuild the order tracking with current data
5636
- fullData.forEach((item, index) => {
5915
+ // Rebuild the order tracking with flattened data
5916
+ flattenedData.forEach((item, index) => {
5637
5917
  const itemId = this.getItemId(item);
5638
5918
  if (itemId) {
5639
5919
  const actualOrder = this.getItemOrder(item);
@@ -5644,18 +5924,21 @@ class CideEleDataGridComponent {
5644
5924
  });
5645
5925
  }
5646
5926
  });
5647
- console.log('🔍 Row order map baseline completely rebuilt from current data:', this.initialDataOrderIds.length, 'items');
5927
+ console.log('🔍 Row order map baseline completely rebuilt from flattened data:', this.initialDataOrderIds.length, 'items (including children)');
5648
5928
  }
5649
5929
  /**
5650
5930
  * Get the current order array from the row order map
5931
+ * Includes all items from hierarchical structure (parents and children)
5651
5932
  */
5652
5933
  getCurrentOrderFromMap() {
5653
5934
  const orderedItems = [];
5654
5935
  // Get the full dataset from the config (not just displayedData)
5655
5936
  const fullData = this.mergedConfig().data || [];
5656
- // Collect all items with their new positions from the full dataset
5937
+ // Flatten the data to include all items (parents and children) for hierarchical structures
5938
+ const flattenedData = this.flattenAllDataForOrderTracking(fullData);
5939
+ // Collect all items with their new positions from the flattened dataset
5657
5940
  this.rowOrderMap.forEach((orderInfo, itemId) => {
5658
- const item = fullData.find(dataItem => this.getItemId(dataItem) === itemId);
5941
+ const item = flattenedData.find(dataItem => this.getItemId(dataItem) === itemId);
5659
5942
  if (item) {
5660
5943
  orderedItems.push({ item, newPosition: orderInfo.newPosition });
5661
5944
  }
@@ -5667,14 +5950,17 @@ class CideEleDataGridComponent {
5667
5950
  }
5668
5951
  /**
5669
5952
  * Get only the items that have actually changed order
5953
+ * Includes all items from hierarchical structure (parents and children)
5670
5954
  */
5671
5955
  getChangedOrders() {
5672
5956
  const changedItems = [];
5673
5957
  // Get the full dataset from the config
5674
5958
  const fullData = this.mergedConfig().data || [];
5959
+ // Flatten the data to include all items (parents and children) for hierarchical structures
5960
+ const flattenedData = this.flattenAllDataForOrderTracking(fullData);
5675
5961
  // Check each item to see if its position has changed
5676
5962
  this.rowOrderMap.forEach((orderInfo, itemId) => {
5677
- const item = fullData.find(dataItem => this.getItemId(dataItem) === itemId);
5963
+ const item = flattenedData.find(dataItem => this.getItemId(dataItem) === itemId);
5678
5964
  if (item && orderInfo.oldPosition !== orderInfo.newPosition) {
5679
5965
  changedItems.push(item);
5680
5966
  console.log('🔍 Item changed order:', itemId, 'from', orderInfo.oldPosition, 'to', orderInfo.newPosition);
@@ -5682,6 +5968,30 @@ class CideEleDataGridComponent {
5682
5968
  });
5683
5969
  return changedItems;
5684
5970
  }
5971
+ /**
5972
+ * Flatten all data for order tracking (includes all hierarchical children)
5973
+ * This method ensures that all items (parents and children) are included in order tracking
5974
+ */
5975
+ flattenAllDataForOrderTracking(data) {
5976
+ const flattenedItems = [];
5977
+ const flattenItems = (items) => {
5978
+ items.forEach(item => {
5979
+ flattenedItems.push(item);
5980
+ // Check if item has children (for hierarchical data)
5981
+ const treeConfig = this.mergedConfig().tree;
5982
+ if (treeConfig?.enabled) {
5983
+ const childrenKey = treeConfig.childrenKey || 'children';
5984
+ const children = this.getNestedValue(item, childrenKey) || [];
5985
+ if (children.length > 0) {
5986
+ flattenItems(children);
5987
+ }
5988
+ }
5989
+ });
5990
+ };
5991
+ flattenItems(data);
5992
+ console.log('🔍 Flattened data for order tracking:', flattenedItems.length, 'items (including children)');
5993
+ return flattenedItems;
5994
+ }
5685
5995
  /**
5686
5996
  * Update local data order for visual reordering (LOCAL ONLY)
5687
5997
  */