cloud-ide-element 1.0.67 → 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: [{
@@ -4811,11 +5017,13 @@ class CideEleDataGridComponent {
4811
5017
  if (this.isDragDropEnabled()) {
4812
5018
  // Get the full dataset from the config (not just displayedData)
4813
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);
4814
5022
  // Completely build the order tracking from current data
4815
5023
  this.rowOrderMap.clear();
4816
5024
  this.initialDataOrderIds = [];
4817
- // Build the order tracking with current data
4818
- fullData.forEach((item, index) => {
5025
+ // Build the order tracking with flattened data
5026
+ flattenedData.forEach((item, index) => {
4819
5027
  const itemId = this.getItemId(item);
4820
5028
  if (itemId) {
4821
5029
  const actualOrder = this.getItemOrder(item);
@@ -4828,7 +5036,7 @@ class CideEleDataGridComponent {
4828
5036
  });
4829
5037
  // Clear any existing local reordered data
4830
5038
  this.localReorderedData = [];
4831
- 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)');
4832
5040
  console.log('🔍 Initial order map created with actual order values:', Array.from(this.rowOrderMap.entries()));
4833
5041
  }
4834
5042
  }
@@ -4839,11 +5047,13 @@ class CideEleDataGridComponent {
4839
5047
  if (this.isDragDropEnabled()) {
4840
5048
  // Get the current full dataset
4841
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);
4842
5052
  // Completely rebuild the order tracking from current data
4843
5053
  this.rowOrderMap.clear();
4844
5054
  this.initialDataOrderIds = [];
4845
- // Rebuild the order tracking with current data
4846
- fullData.forEach((item, index) => {
5055
+ // Rebuild the order tracking with flattened data
5056
+ flattenedData.forEach((item, index) => {
4847
5057
  const itemId = this.getItemId(item);
4848
5058
  if (itemId) {
4849
5059
  const actualOrder = this.getItemOrder(item);
@@ -5663,15 +5873,18 @@ class CideEleDataGridComponent {
5663
5873
  }
5664
5874
  /**
5665
5875
  * Reset the row order map to original positions (for reset action)
5876
+ * Includes all items from hierarchical structure (parents and children)
5666
5877
  */
5667
5878
  resetRowOrderMap() {
5668
5879
  // Get the current full dataset from the config (not cached data)
5669
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);
5670
5883
  // Completely rebuild the order tracking from current data
5671
5884
  this.rowOrderMap.clear();
5672
5885
  this.initialDataOrderIds = [];
5673
- // Rebuild the order tracking with current data
5674
- fullData.forEach((item, index) => {
5886
+ // Rebuild the order tracking with flattened data
5887
+ flattenedData.forEach((item, index) => {
5675
5888
  const itemId = this.getItemId(item);
5676
5889
  if (itemId) {
5677
5890
  const actualOrder = this.getItemOrder(item);
@@ -5685,19 +5898,22 @@ class CideEleDataGridComponent {
5685
5898
  // Clear any local reordered data
5686
5899
  this.localReorderedData = [];
5687
5900
  this.hasOrderChanged.set(false);
5688
- 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)');
5689
5902
  }
5690
5903
  /**
5691
5904
  * Update the row order map baseline to current positions (for save action)
5905
+ * Includes all items from hierarchical structure (parents and children)
5692
5906
  */
5693
5907
  updateRowOrderMapBaseline() {
5694
5908
  // Get the current full dataset from the config
5695
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);
5696
5912
  // Completely rebuild the order tracking from current data
5697
5913
  this.rowOrderMap.clear();
5698
5914
  this.initialDataOrderIds = [];
5699
- // Rebuild the order tracking with current data
5700
- fullData.forEach((item, index) => {
5915
+ // Rebuild the order tracking with flattened data
5916
+ flattenedData.forEach((item, index) => {
5701
5917
  const itemId = this.getItemId(item);
5702
5918
  if (itemId) {
5703
5919
  const actualOrder = this.getItemOrder(item);
@@ -5708,18 +5924,21 @@ class CideEleDataGridComponent {
5708
5924
  });
5709
5925
  }
5710
5926
  });
5711
- 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)');
5712
5928
  }
5713
5929
  /**
5714
5930
  * Get the current order array from the row order map
5931
+ * Includes all items from hierarchical structure (parents and children)
5715
5932
  */
5716
5933
  getCurrentOrderFromMap() {
5717
5934
  const orderedItems = [];
5718
5935
  // Get the full dataset from the config (not just displayedData)
5719
5936
  const fullData = this.mergedConfig().data || [];
5720
- // 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
5721
5940
  this.rowOrderMap.forEach((orderInfo, itemId) => {
5722
- const item = fullData.find(dataItem => this.getItemId(dataItem) === itemId);
5941
+ const item = flattenedData.find(dataItem => this.getItemId(dataItem) === itemId);
5723
5942
  if (item) {
5724
5943
  orderedItems.push({ item, newPosition: orderInfo.newPosition });
5725
5944
  }
@@ -5731,14 +5950,17 @@ class CideEleDataGridComponent {
5731
5950
  }
5732
5951
  /**
5733
5952
  * Get only the items that have actually changed order
5953
+ * Includes all items from hierarchical structure (parents and children)
5734
5954
  */
5735
5955
  getChangedOrders() {
5736
5956
  const changedItems = [];
5737
5957
  // Get the full dataset from the config
5738
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);
5739
5961
  // Check each item to see if its position has changed
5740
5962
  this.rowOrderMap.forEach((orderInfo, itemId) => {
5741
- const item = fullData.find(dataItem => this.getItemId(dataItem) === itemId);
5963
+ const item = flattenedData.find(dataItem => this.getItemId(dataItem) === itemId);
5742
5964
  if (item && orderInfo.oldPosition !== orderInfo.newPosition) {
5743
5965
  changedItems.push(item);
5744
5966
  console.log('🔍 Item changed order:', itemId, 'from', orderInfo.oldPosition, 'to', orderInfo.newPosition);
@@ -5746,6 +5968,30 @@ class CideEleDataGridComponent {
5746
5968
  });
5747
5969
  return changedItems;
5748
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
+ }
5749
5995
  /**
5750
5996
  * Update local data order for visual reordering (LOCAL ONLY)
5751
5997
  */