@cqa-lib/cqa-ui 1.1.549 → 1.1.551

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.
@@ -960,6 +960,18 @@ class DialogComponent {
960
960
  }
961
961
  return !!d;
962
962
  }
963
+ isButtonLoading(button) {
964
+ const l = button.loading;
965
+ if (typeof l === 'function') {
966
+ try {
967
+ return !!l(this.dialogRef);
968
+ }
969
+ catch {
970
+ return false;
971
+ }
972
+ }
973
+ return !!l;
974
+ }
963
975
  buttonHostClasses(button) {
964
976
  const role = this.normalizeRole(button.role);
965
977
  if (role === 'warn') {
@@ -987,10 +999,10 @@ class DialogComponent {
987
999
  }
988
1000
  }
989
1001
  DialogComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: DialogComponent, deps: [{ token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
990
- DialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: DialogComponent, selector: "cqa-dialog", host: { classAttribute: "cqa-ui-root" }, viewQueries: [{ propertyName: "portalOutlet", first: true, predicate: CdkPortalOutlet, descendants: true, static: true }], ngImport: i0, template: "<div class=\"cqa-ui-root\">\n <div class=\"cqa-flex cqa-w-full cqa-justify-center cqa-px-4 sm:cqa-px-6\">\n <div [ngClass]=\"panelClassList\" [ngStyle]=\"panelStyles\">\n <div class=\"cqa-flex cqa-flex-col cqa-gap-5\">\n <div class=\"cqa-flex cqa-flex-col cqa-gap-3\">\n <h2 class=\"cqa-text-lg cqa-font-semibold cqa-text-dialog\">\n {{ config.title }}\n </h2>\n\n <p *ngIf=\"config.description\" class=\"cqa-text-sm cqa-leading-6 cqa-text-dialog-secondary\">\n {{ config.description }}\n </p>\n\n <div *ngIf=\"config.warning\"\n class=\"cqa-rounded-xl cqa-border cqa-border-red-200 cqa-bg-red-50 cqa-px-4 cqa-py-3 cqa-text-sm cqa-leading-5 cqa-text-red-700\">\n {{ config.warning }}\n </div>\n </div>\n\n <div class=\"cqa-text-sm cqa-text-dialog\" [class.hidden]=\"!contentAttached\">\n <ng-template cdkPortalOutlet></ng-template>\n </div>\n\n <div class=\"cqa-mt-4 cqa-flex cqa-flex-wrap cqa-gap-3\" [ngClass]=\"buttonAlignmentClass\">\n <cqa-button *ngFor=\"let button of config.buttons\" type=\"button\" [variant]=\"buttonVariant(button)\"\n [disabled]=\"isButtonDisabled(button)\"\n [ngClass]=\"buttonHostClasses(button)\" (clicked)=\"onButtonClick(button)\">\n {{ button.label }}\n </cqa-button>\n </div>\n </div>\n </div>\n </div>\n</div>", components: [{ type: ButtonComponent, selector: "cqa-button", inputs: ["variant", "btnSize", "disabled", "loading", "icon", "iconPosition", "fullWidth", "iconColor", "type", "text", "customClass", "inlineStyles", "tooltip", "tooltipPosition"], outputs: ["clicked"] }], directives: [{ type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1002
+ DialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: DialogComponent, selector: "cqa-dialog", host: { classAttribute: "cqa-ui-root" }, viewQueries: [{ propertyName: "portalOutlet", first: true, predicate: CdkPortalOutlet, descendants: true, static: true }], ngImport: i0, template: "<div class=\"cqa-ui-root\">\n <div class=\"cqa-flex cqa-w-full cqa-justify-center cqa-px-4 sm:cqa-px-6\">\n <div [ngClass]=\"panelClassList\" [ngStyle]=\"panelStyles\">\n <div class=\"cqa-flex cqa-flex-col cqa-gap-5\">\n <div class=\"cqa-flex cqa-flex-col cqa-gap-3\">\n <h2 class=\"cqa-text-lg cqa-font-semibold cqa-text-dialog\">\n {{ config.title }}\n </h2>\n\n <p *ngIf=\"config.description\" class=\"cqa-text-sm cqa-leading-6 cqa-text-dialog-secondary\">\n {{ config.description }}\n </p>\n\n <div *ngIf=\"config.warning\"\n class=\"cqa-rounded-xl cqa-border cqa-border-red-200 cqa-bg-red-50 cqa-px-4 cqa-py-3 cqa-text-sm cqa-leading-5 cqa-text-red-700\">\n {{ config.warning }}\n </div>\n </div>\n\n <div class=\"cqa-text-sm cqa-text-dialog\" [class.hidden]=\"!contentAttached\">\n <ng-template cdkPortalOutlet></ng-template>\n </div>\n\n <div class=\"cqa-mt-4 cqa-flex cqa-flex-wrap cqa-gap-3\" [ngClass]=\"buttonAlignmentClass\">\n <cqa-button *ngFor=\"let button of config.buttons\" type=\"button\" [variant]=\"buttonVariant(button)\"\n [disabled]=\"isButtonDisabled(button)\"\n [loading]=\"isButtonLoading(button)\"\n [ngClass]=\"buttonHostClasses(button)\" (clicked)=\"onButtonClick(button)\">\n {{ button.label }}\n </cqa-button>\n </div>\n </div>\n </div>\n </div>\n</div>", components: [{ type: ButtonComponent, selector: "cqa-button", inputs: ["variant", "btnSize", "disabled", "loading", "icon", "iconPosition", "fullWidth", "iconColor", "type", "text", "customClass", "inlineStyles", "tooltip", "tooltipPosition"], outputs: ["clicked"] }], directives: [{ type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i3.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
991
1003
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: DialogComponent, decorators: [{
992
1004
  type: Component,
993
- args: [{ selector: 'cqa-dialog', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'cqa-ui-root' }, template: "<div class=\"cqa-ui-root\">\n <div class=\"cqa-flex cqa-w-full cqa-justify-center cqa-px-4 sm:cqa-px-6\">\n <div [ngClass]=\"panelClassList\" [ngStyle]=\"panelStyles\">\n <div class=\"cqa-flex cqa-flex-col cqa-gap-5\">\n <div class=\"cqa-flex cqa-flex-col cqa-gap-3\">\n <h2 class=\"cqa-text-lg cqa-font-semibold cqa-text-dialog\">\n {{ config.title }}\n </h2>\n\n <p *ngIf=\"config.description\" class=\"cqa-text-sm cqa-leading-6 cqa-text-dialog-secondary\">\n {{ config.description }}\n </p>\n\n <div *ngIf=\"config.warning\"\n class=\"cqa-rounded-xl cqa-border cqa-border-red-200 cqa-bg-red-50 cqa-px-4 cqa-py-3 cqa-text-sm cqa-leading-5 cqa-text-red-700\">\n {{ config.warning }}\n </div>\n </div>\n\n <div class=\"cqa-text-sm cqa-text-dialog\" [class.hidden]=\"!contentAttached\">\n <ng-template cdkPortalOutlet></ng-template>\n </div>\n\n <div class=\"cqa-mt-4 cqa-flex cqa-flex-wrap cqa-gap-3\" [ngClass]=\"buttonAlignmentClass\">\n <cqa-button *ngFor=\"let button of config.buttons\" type=\"button\" [variant]=\"buttonVariant(button)\"\n [disabled]=\"isButtonDisabled(button)\"\n [ngClass]=\"buttonHostClasses(button)\" (clicked)=\"onButtonClick(button)\">\n {{ button.label }}\n </cqa-button>\n </div>\n </div>\n </div>\n </div>\n</div>", styles: [] }]
1005
+ args: [{ selector: 'cqa-dialog', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'cqa-ui-root' }, template: "<div class=\"cqa-ui-root\">\n <div class=\"cqa-flex cqa-w-full cqa-justify-center cqa-px-4 sm:cqa-px-6\">\n <div [ngClass]=\"panelClassList\" [ngStyle]=\"panelStyles\">\n <div class=\"cqa-flex cqa-flex-col cqa-gap-5\">\n <div class=\"cqa-flex cqa-flex-col cqa-gap-3\">\n <h2 class=\"cqa-text-lg cqa-font-semibold cqa-text-dialog\">\n {{ config.title }}\n </h2>\n\n <p *ngIf=\"config.description\" class=\"cqa-text-sm cqa-leading-6 cqa-text-dialog-secondary\">\n {{ config.description }}\n </p>\n\n <div *ngIf=\"config.warning\"\n class=\"cqa-rounded-xl cqa-border cqa-border-red-200 cqa-bg-red-50 cqa-px-4 cqa-py-3 cqa-text-sm cqa-leading-5 cqa-text-red-700\">\n {{ config.warning }}\n </div>\n </div>\n\n <div class=\"cqa-text-sm cqa-text-dialog\" [class.hidden]=\"!contentAttached\">\n <ng-template cdkPortalOutlet></ng-template>\n </div>\n\n <div class=\"cqa-mt-4 cqa-flex cqa-flex-wrap cqa-gap-3\" [ngClass]=\"buttonAlignmentClass\">\n <cqa-button *ngFor=\"let button of config.buttons\" type=\"button\" [variant]=\"buttonVariant(button)\"\n [disabled]=\"isButtonDisabled(button)\"\n [loading]=\"isButtonLoading(button)\"\n [ngClass]=\"buttonHostClasses(button)\" (clicked)=\"onButtonClick(button)\">\n {{ button.label }}\n </cqa-button>\n </div>\n </div>\n </div>\n </div>\n</div>", styles: [] }]
994
1006
  }], ctorParameters: function () { return [{ type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { portalOutlet: [{
995
1007
  type: ViewChild,
996
1008
  args: [CdkPortalOutlet, { static: true }]
@@ -7801,12 +7813,16 @@ const DEFAULT_MODULAR_LABELS = {
7801
7813
  folderMenuDuplicate: 'Duplicate folder',
7802
7814
  folderMenuDelete: 'Delete folder',
7803
7815
  deleteFolderDialogTitle: 'Delete folder',
7804
- deleteFolderDialogBodySingular: 'This folder contains 1 test case. What should happen to them?',
7805
- deleteFolderDialogBodyPlural: 'This folder contains {n} test cases. What should happen to them?',
7806
- deleteFolderDialogMoveToParentTitle: 'Move test cases to parent folder',
7807
- deleteFolderDialogMoveToParentDescription: 'Test cases move one level up — nothing is lost',
7808
- deleteFolderDialogMoveToUnorganizedTitle: 'Move test cases to Unorganized',
7809
- deleteFolderDialogMoveToUnorganizedDescription: 'Test cases return to the Unorganized section',
7816
+ deleteFolderDialogBodyTemplate: 'This folder contains {items}. What should happen to them?',
7817
+ deleteFolderDialogBodyTestCaseSingular: '1 test case',
7818
+ deleteFolderDialogBodyTestCasePlural: '{n} test cases',
7819
+ deleteFolderDialogBodyStepGroupSingular: '1 step group',
7820
+ deleteFolderDialogBodyStepGroupPlural: '{n} step groups',
7821
+ deleteFolderDialogBodyJoin: 'and',
7822
+ deleteFolderDialogMoveToParentTitle: 'Move to parent folder',
7823
+ deleteFolderDialogMoveToParentDescription: 'Move them one level up — nothing is lost',
7824
+ deleteFolderDialogMoveToUnorganizedTitle: 'Move to Unorganized',
7825
+ deleteFolderDialogMoveToUnorganizedDescription: 'Move them to the Unorganized section',
7810
7826
  deleteFolderDialogCancel: 'Cancel',
7811
7827
  deleteFolderDialogConfirm: 'Delete folder',
7812
7828
  emptyNoTestsInFolderTitle: 'No Test Case Found',
@@ -7814,6 +7830,8 @@ const DEFAULT_MODULAR_LABELS = {
7814
7830
  folderColumnLabel: 'Folder',
7815
7831
  unorganizedRowLabel: 'Unorganized',
7816
7832
  noFoldersFound: 'No Folders Found',
7833
+ folderTooltipTotalSingular: '1 total test case including subfolders',
7834
+ folderTooltipTotalPlural: '{n} total test cases including subfolders',
7817
7835
  };
7818
7836
  const DEFAULT_REORDER_LABELS = {
7819
7837
  reorderButton: 'Reorder',
@@ -7951,14 +7969,14 @@ MoveToFolderDialogComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.
7951
7969
  MoveToFolderDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: MoveToFolderDialogComponent, selector: "cqa-move-to-folder-dialog", inputs: { folders: "folders", labels: "labels", currentFolderId: "currentFolderId", pickedFolderId: "pickedFolderId", initialExpandedIds: "initialExpandedIds", rootDisabled: "rootDisabled", pickerHeight: "pickerHeight" }, outputs: { folderPicked: "folderPicked", pickedFolderIdChange: "pickedFolderIdChange" }, host: { classAttribute: "cqa-ui-root" }, usesOnChanges: true, ngImport: i0, template: `
7952
7970
  <!-- Reusable outlined folder icon. Stroke uses currentColor so the row's text color drives the icon. -->
7953
7971
  <ng-template #folderIconTpl>
7954
- <svg width="17" height="15" viewBox="0 0 17 15" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="cqa-shrink-0">
7972
+ <svg width="17" height="15" viewBox="0 0 17 15" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="cqa-flex-shrink-0">
7955
7973
  <path d="M15.6375 12.6377C15.6375 13.0355 15.4794 13.4171 15.1981 13.6984C14.9168 13.9797 14.5353 14.1377 14.1375 14.1377H2.13745C1.73963 14.1377 1.3581 13.9797 1.07679 13.6984C0.795486 13.4171 0.637451 13.0355 0.637451 12.6377V2.1377C0.637451 1.73987 0.795486 1.35834 1.07679 1.07704C1.3581 0.795731 1.73963 0.637695 2.13745 0.637695H5.88745L7.38745 2.8877H14.1375C14.5353 2.8877 14.9168 3.04573 15.1981 3.32704C15.4794 3.60834 15.6375 3.98987 15.6375 4.3877V12.6377Z" stroke="currentColor" stroke-width="1.275" stroke-linecap="round" stroke-linejoin="round"/>
7956
7974
  </svg>
7957
7975
  </ng-template>
7958
7976
 
7959
7977
  <!-- Reusable outlined archive/inbox icon for the "Unorganized" entry. -->
7960
7978
  <ng-template #unorganizedIconTpl>
7961
- <svg width="17" height="15" viewBox="0 0 17 15" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="cqa-shrink-0">
7979
+ <svg width="17" height="15" viewBox="0 0 17 15" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="cqa-flex-shrink-0">
7962
7980
  <path d="M14.6875 5.5V12.5C14.6875 13.3 14.0375 14 13.1875 14H3.8125C2.9625 14 2.3125 13.3 2.3125 12.5V5.5M14.6875 5.5L12.7875 1.7C12.5375 1.2 12.0875 1 11.5875 1H5.4125C4.9125 1 4.4625 1.2 4.2125 1.7L2.3125 5.5M14.6875 5.5H2.3125M6.1875 8.5H10.8125" stroke="currentColor" stroke-width="1.275" stroke-linecap="round" stroke-linejoin="round"/>
7963
7981
  </svg>
7964
7982
  </ng-template>
@@ -7978,7 +7996,7 @@ MoveToFolderDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.
7978
7996
  (keydown.space)="$event.preventDefault(); !isUnorganizedDisabled && pick(null)"
7979
7997
  >
7980
7998
  <!-- chevron-slot placeholder so all rows line up -->
7981
- <span class="cqa-inline-block cqa-w-5 cqa-h-5 cqa-shrink-0"></span>
7999
+ <span class="cqa-inline-block cqa-w-5 cqa-h-5 cqa-flex-shrink-0"></span>
7982
8000
  <ng-container *ngTemplateOutlet="unorganizedIconTpl"></ng-container>
7983
8001
  <span>{{ labels.moveDialogRoot }}</span>
7984
8002
  </div>
@@ -8001,7 +8019,7 @@ MoveToFolderDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.
8001
8019
  <button
8002
8020
  *ngIf="row.hasChildren; else chevronPlaceholder"
8003
8021
  type="button"
8004
- class="cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-5 cqa-h-5 cqa-shrink-0 cqa-rounded cqa-text-neutral-600 hover:cqa-bg-neutral-100 hover:cqa-text-neutral-900"
8022
+ class="cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-5 cqa-h-5 cqa-flex-shrink-0 cqa-rounded cqa-text-neutral-600 hover:cqa-bg-neutral-100 hover:cqa-text-neutral-900"
8005
8023
  [attr.aria-label]="row.expanded ? 'Collapse ' + row.name : 'Expand ' + row.name"
8006
8024
  (click)="toggleExpanded(row.id!, $event)"
8007
8025
  >
@@ -8011,7 +8029,7 @@ MoveToFolderDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.
8011
8029
  </svg>
8012
8030
  </button>
8013
8031
  <ng-template #chevronPlaceholder>
8014
- <span class="cqa-inline-block cqa-w-5 cqa-h-5 cqa-shrink-0"></span>
8032
+ <span class="cqa-inline-block cqa-w-5 cqa-h-5 cqa-flex-shrink-0"></span>
8015
8033
  </ng-template>
8016
8034
 
8017
8035
  <ng-container *ngTemplateOutlet="folderIconTpl"></ng-container>
@@ -8024,14 +8042,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8024
8042
  args: [{ selector: 'cqa-move-to-folder-dialog', template: `
8025
8043
  <!-- Reusable outlined folder icon. Stroke uses currentColor so the row's text color drives the icon. -->
8026
8044
  <ng-template #folderIconTpl>
8027
- <svg width="17" height="15" viewBox="0 0 17 15" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="cqa-shrink-0">
8045
+ <svg width="17" height="15" viewBox="0 0 17 15" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="cqa-flex-shrink-0">
8028
8046
  <path d="M15.6375 12.6377C15.6375 13.0355 15.4794 13.4171 15.1981 13.6984C14.9168 13.9797 14.5353 14.1377 14.1375 14.1377H2.13745C1.73963 14.1377 1.3581 13.9797 1.07679 13.6984C0.795486 13.4171 0.637451 13.0355 0.637451 12.6377V2.1377C0.637451 1.73987 0.795486 1.35834 1.07679 1.07704C1.3581 0.795731 1.73963 0.637695 2.13745 0.637695H5.88745L7.38745 2.8877H14.1375C14.5353 2.8877 14.9168 3.04573 15.1981 3.32704C15.4794 3.60834 15.6375 3.98987 15.6375 4.3877V12.6377Z" stroke="currentColor" stroke-width="1.275" stroke-linecap="round" stroke-linejoin="round"/>
8029
8047
  </svg>
8030
8048
  </ng-template>
8031
8049
 
8032
8050
  <!-- Reusable outlined archive/inbox icon for the "Unorganized" entry. -->
8033
8051
  <ng-template #unorganizedIconTpl>
8034
- <svg width="17" height="15" viewBox="0 0 17 15" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="cqa-shrink-0">
8052
+ <svg width="17" height="15" viewBox="0 0 17 15" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="cqa-flex-shrink-0">
8035
8053
  <path d="M14.6875 5.5V12.5C14.6875 13.3 14.0375 14 13.1875 14H3.8125C2.9625 14 2.3125 13.3 2.3125 12.5V5.5M14.6875 5.5L12.7875 1.7C12.5375 1.2 12.0875 1 11.5875 1H5.4125C4.9125 1 4.4625 1.2 4.2125 1.7L2.3125 5.5M14.6875 5.5H2.3125M6.1875 8.5H10.8125" stroke="currentColor" stroke-width="1.275" stroke-linecap="round" stroke-linejoin="round"/>
8036
8054
  </svg>
8037
8055
  </ng-template>
@@ -8051,7 +8069,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8051
8069
  (keydown.space)="$event.preventDefault(); !isUnorganizedDisabled && pick(null)"
8052
8070
  >
8053
8071
  <!-- chevron-slot placeholder so all rows line up -->
8054
- <span class="cqa-inline-block cqa-w-5 cqa-h-5 cqa-shrink-0"></span>
8072
+ <span class="cqa-inline-block cqa-w-5 cqa-h-5 cqa-flex-shrink-0"></span>
8055
8073
  <ng-container *ngTemplateOutlet="unorganizedIconTpl"></ng-container>
8056
8074
  <span>{{ labels.moveDialogRoot }}</span>
8057
8075
  </div>
@@ -8074,7 +8092,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8074
8092
  <button
8075
8093
  *ngIf="row.hasChildren; else chevronPlaceholder"
8076
8094
  type="button"
8077
- class="cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-5 cqa-h-5 cqa-shrink-0 cqa-rounded cqa-text-neutral-600 hover:cqa-bg-neutral-100 hover:cqa-text-neutral-900"
8095
+ class="cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-5 cqa-h-5 cqa-flex-shrink-0 cqa-rounded cqa-text-neutral-600 hover:cqa-bg-neutral-100 hover:cqa-text-neutral-900"
8078
8096
  [attr.aria-label]="row.expanded ? 'Collapse ' + row.name : 'Expand ' + row.name"
8079
8097
  (click)="toggleExpanded(row.id!, $event)"
8080
8098
  >
@@ -8084,7 +8102,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8084
8102
  </svg>
8085
8103
  </button>
8086
8104
  <ng-template #chevronPlaceholder>
8087
- <span class="cqa-inline-block cqa-w-5 cqa-h-5 cqa-shrink-0"></span>
8105
+ <span class="cqa-inline-block cqa-w-5 cqa-h-5 cqa-flex-shrink-0"></span>
8088
8106
  </ng-template>
8089
8107
 
8090
8108
  <ng-container *ngTemplateOutlet="folderIconTpl"></ng-container>
@@ -8282,7 +8300,10 @@ NewFolderDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0
8282
8300
  ></cqa-move-to-folder-dialog>
8283
8301
  </div>
8284
8302
 
8285
- <!-- Folder color (optional) -->
8303
+ <!-- Folder color picker temporarily disabled — submitted folders fall
8304
+ back to DEFAULT_FOLDER_COLOR. Re-enable by uncommenting the block
8305
+ below; all inputs/outputs are still wired. -->
8306
+ <!--
8286
8307
  <div class="cqa-flex cqa-flex-col cqa-gap-2">
8287
8308
  <label class="cqa-text-sm cqa-font-medium cqa-text-neutral-700">
8288
8309
  {{ labels.newFolderDialogColorLabel }}
@@ -8338,8 +8359,9 @@ NewFolderDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0
8338
8359
  </label>
8339
8360
  </div>
8340
8361
  </div>
8362
+ -->
8341
8363
  </div>
8342
- `, isInline: true, components: [{ type: CustomInputComponent, selector: "cqa-custom-input", inputs: ["inputId", "label", "type", "placeholder", "value", "disabled", "errors", "required", "ariaLabel", "size", "fullWidth", "maxLength", "showCharCount", "inputInlineStyle", "labelInlineStyle"], outputs: ["valueChange", "blurred", "focused", "enterPressed"] }, { type: MoveToFolderDialogComponent, selector: "cqa-move-to-folder-dialog", inputs: ["folders", "labels", "currentFolderId", "pickedFolderId", "initialExpandedIds", "rootDisabled", "pickerHeight"], outputs: ["folderPicked", "pickedFolderIdChange"] }], directives: [{ type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8364
+ `, isInline: true, components: [{ type: CustomInputComponent, selector: "cqa-custom-input", inputs: ["inputId", "label", "type", "placeholder", "value", "disabled", "errors", "required", "ariaLabel", "size", "fullWidth", "maxLength", "showCharCount", "inputInlineStyle", "labelInlineStyle"], outputs: ["valueChange", "blurred", "focused", "enterPressed"] }, { type: MoveToFolderDialogComponent, selector: "cqa-move-to-folder-dialog", inputs: ["folders", "labels", "currentFolderId", "pickedFolderId", "initialExpandedIds", "rootDisabled", "pickerHeight"], outputs: ["folderPicked", "pickedFolderIdChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8343
8365
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: NewFolderDialogComponent, decorators: [{
8344
8366
  type: Component,
8345
8367
  args: [{ selector: 'cqa-new-folder-dialog', template: `
@@ -8370,7 +8392,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8370
8392
  ></cqa-move-to-folder-dialog>
8371
8393
  </div>
8372
8394
 
8373
- <!-- Folder color (optional) -->
8395
+ <!-- Folder color picker temporarily disabled — submitted folders fall
8396
+ back to DEFAULT_FOLDER_COLOR. Re-enable by uncommenting the block
8397
+ below; all inputs/outputs are still wired. -->
8398
+ <!--
8374
8399
  <div class="cqa-flex cqa-flex-col cqa-gap-2">
8375
8400
  <label class="cqa-text-sm cqa-font-medium cqa-text-neutral-700">
8376
8401
  {{ labels.newFolderDialogColorLabel }}
@@ -8426,6 +8451,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8426
8451
  </label>
8427
8452
  </div>
8428
8453
  </div>
8454
+ -->
8429
8455
  </div>
8430
8456
  `, host: { class: 'cqa-ui-root' }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [] }]
8431
8457
  }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { folders: [{
@@ -8468,6 +8494,7 @@ class DeleteFolderDialogComponent {
8468
8494
  this.cdr = cdr;
8469
8495
  this.folderName = '';
8470
8496
  this.testCount = 0;
8497
+ this.stepGroupCount = 0;
8471
8498
  this.hasParent = false;
8472
8499
  this.labels = { ...DEFAULT_MODULAR_LABELS };
8473
8500
  this.strategy = 'MOVE_TO_PARENT';
@@ -8487,10 +8514,21 @@ class DeleteFolderDialogComponent {
8487
8514
  }
8488
8515
  }
8489
8516
  get bodyText() {
8490
- const template = this.testCount === 1
8491
- ? this.labels.deleteFolderDialogBodySingular
8492
- : this.labels.deleteFolderDialogBodyPlural;
8493
- return template.replace('{n}', '' + this.testCount);
8517
+ const parts = [];
8518
+ if (this.testCount > 0) {
8519
+ const tpl = this.testCount === 1
8520
+ ? this.labels.deleteFolderDialogBodyTestCaseSingular
8521
+ : this.labels.deleteFolderDialogBodyTestCasePlural;
8522
+ parts.push(tpl.replace('{n}', '' + this.testCount));
8523
+ }
8524
+ if (this.stepGroupCount > 0) {
8525
+ const tpl = this.stepGroupCount === 1
8526
+ ? this.labels.deleteFolderDialogBodyStepGroupSingular
8527
+ : this.labels.deleteFolderDialogBodyStepGroupPlural;
8528
+ parts.push(tpl.replace('{n}', '' + this.stepGroupCount));
8529
+ }
8530
+ const items = parts.join(` ${this.labels.deleteFolderDialogBodyJoin} `);
8531
+ return this.labels.deleteFolderDialogBodyTemplate.replace('{items}', items);
8494
8532
  }
8495
8533
  setStrategy(value) {
8496
8534
  if (this.strategy === value)
@@ -8501,7 +8539,7 @@ class DeleteFolderDialogComponent {
8501
8539
  }
8502
8540
  }
8503
8541
  DeleteFolderDialogComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: DeleteFolderDialogComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
8504
- DeleteFolderDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: DeleteFolderDialogComponent, selector: "cqa-delete-folder-dialog", inputs: { folderName: "folderName", testCount: "testCount", hasParent: "hasParent", labels: "labels", strategy: "strategy" }, outputs: { strategyChange: "strategyChange" }, host: { classAttribute: "cqa-ui-root" }, usesOnChanges: true, ngImport: i0, template: `
8542
+ DeleteFolderDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: DeleteFolderDialogComponent, selector: "cqa-delete-folder-dialog", inputs: { folderName: "folderName", testCount: "testCount", stepGroupCount: "stepGroupCount", hasParent: "hasParent", labels: "labels", strategy: "strategy" }, outputs: { strategyChange: "strategyChange" }, host: { classAttribute: "cqa-ui-root" }, usesOnChanges: true, ngImport: i0, template: `
8505
8543
  <div class="cqa-flex cqa-flex-col cqa-gap-4 cqa-w-full">
8506
8544
  <p class="cqa-text-sm cqa-text-neutral-800 cqa-m-0">{{ bodyText }}</p>
8507
8545
 
@@ -8548,7 +8586,7 @@ DeleteFolderDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.
8548
8586
  <!-- Reusable radio bullet -->
8549
8587
  <ng-template #radio let-checked="checked">
8550
8588
  <span
8551
- class="cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-5 cqa-h-5 cqa-rounded-full cqa-border-solid cqa-shrink-0 cqa-mt-0.5"
8589
+ class="cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-5 cqa-h-5 cqa-rounded-full cqa-border-solid cqa-flex-shrink-0 cqa-mt-0.5"
8552
8590
  [ngClass]="checked ? 'cqa-border-indigo-600' : 'cqa-border-neutral-400'"
8553
8591
  [style.borderWidth]="'2px'"
8554
8592
  >
@@ -8605,7 +8643,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8605
8643
  <!-- Reusable radio bullet -->
8606
8644
  <ng-template #radio let-checked="checked">
8607
8645
  <span
8608
- class="cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-5 cqa-h-5 cqa-rounded-full cqa-border-solid cqa-shrink-0 cqa-mt-0.5"
8646
+ class="cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-5 cqa-h-5 cqa-rounded-full cqa-border-solid cqa-flex-shrink-0 cqa-mt-0.5"
8609
8647
  [ngClass]="checked ? 'cqa-border-indigo-600' : 'cqa-border-neutral-400'"
8610
8648
  [style.borderWidth]="'2px'"
8611
8649
  >
@@ -8617,6 +8655,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8617
8655
  type: Input
8618
8656
  }], testCount: [{
8619
8657
  type: Input
8658
+ }], stepGroupCount: [{
8659
+ type: Input
8620
8660
  }], hasParent: [{
8621
8661
  type: Input
8622
8662
  }], labels: [{
@@ -8906,6 +8946,7 @@ class FolderSidebarComponent {
8906
8946
  this.allowDrop = true;
8907
8947
  this.showCounts = true;
8908
8948
  this.collapsed = false;
8949
+ this.foldersAccordionExpanded = true;
8909
8950
  this.labels = { ...DEFAULT_MODULAR_LABELS };
8910
8951
  /** When true, the sidebar emits `searchChange` and skips its local name filter
8911
8952
  * — the host is expected to swap `folders` with server-side search results.
@@ -8924,6 +8965,11 @@ class FolderSidebarComponent {
8924
8965
  * spinner; when set during pagination, a small spinner appears at the
8925
8966
  * bottom of the tree. */
8926
8967
  this.rootFoldersLoading = false;
8968
+ /** Folder ids currently being saved (rename / move / etc). The sidebar renders
8969
+ * a small spinner in place of the count so the user gets feedback while the
8970
+ * host's API call is in flight. The host is responsible for adding/removing
8971
+ * ids around its async call. */
8972
+ this.savingFolderIds = [];
8927
8973
  this.folderSelected = new EventEmitter();
8928
8974
  this.folderExpansionToggled = new EventEmitter();
8929
8975
  /** Lazy-load: fires when the user expands a node whose children haven't been
@@ -8953,6 +8999,7 @@ class FolderSidebarComponent {
8953
8999
  /** Fires when a folder row is dropped onto another folder (or the Unorganized row). */
8954
9000
  this.folderDropped = new EventEmitter();
8955
9001
  this.collapsedChange = new EventEmitter();
9002
+ this.foldersAccordionExpandedChange = new EventEmitter();
8956
9003
  /** Bound to the search input. Exposed as an Input so hosts can seed/restore
8957
9004
  * the visible search term across destroy/re-create cycles (e.g., when the
8958
9005
  * sidebar is `*ngIf`'d out by a view-mode toggle and re-mounted later). */
@@ -8996,8 +9043,35 @@ class FolderSidebarComponent {
8996
9043
  * Used to throttle the scroll-based pagination so we don't re-emit until the
8997
9044
  * host has actually appended a new page. */
8998
9045
  this.lastRootLoadEmittedAt = -1;
9046
+ /** Toggles the inline folder-search input. When closing while a query is active,
9047
+ * also clear the search so the tree reverts to its full state. */
9048
+ this.searchVisible = false;
8999
9049
  this.trackByRow = (_, row) => `${row.kind}_${row.node.id}`;
9000
9050
  }
9051
+ isFolderSaving(id) {
9052
+ return this.savingFolderIds?.includes(id) ?? false;
9053
+ }
9054
+ folderRowTooltip(node) {
9055
+ const name = node?.name || '';
9056
+ if (node?.totalCount == null) {
9057
+ return name || null;
9058
+ }
9059
+ const tpl = node.totalCount === 1
9060
+ ? this.labels.folderTooltipTotalSingular
9061
+ : this.labels.folderTooltipTotalPlural;
9062
+ const countLine = (tpl || '').replace('{n}', String(node.totalCount));
9063
+ return name ? `${name}\n${countLine}` : countLine;
9064
+ }
9065
+ /** Visual depth indent: linear up to a cap, then quarter-rate beyond, so deep
9066
+ * trees don't push the row content off the right edge of the panel. */
9067
+ rowIndent(depth) {
9068
+ const step = 16;
9069
+ const cap = 6;
9070
+ const base = 8;
9071
+ if (depth <= cap)
9072
+ return base + depth * step;
9073
+ return base + cap * step + (depth - cap) * 4;
9074
+ }
9001
9075
  get expandedSet() {
9002
9076
  return new Set(this.expandedFolderIds || []);
9003
9077
  }
@@ -9360,8 +9434,28 @@ class FolderSidebarComponent {
9360
9434
  }
9361
9435
  togglePanel() {
9362
9436
  this.collapsed = !this.collapsed;
9437
+ if (this.collapsed)
9438
+ this.searchVisible = false;
9363
9439
  this.collapsedChange.emit(this.collapsed);
9364
9440
  }
9441
+ toggleFoldersAccordion() {
9442
+ this.foldersAccordionExpanded = !this.foldersAccordionExpanded;
9443
+ if (!this.foldersAccordionExpanded)
9444
+ this.searchVisible = false;
9445
+ this.foldersAccordionExpandedChange.emit(this.foldersAccordionExpanded);
9446
+ this.cdr.markForCheck();
9447
+ }
9448
+ toggleSearchVisible() {
9449
+ if (!this.foldersAccordionExpanded && !this.searchVisible) {
9450
+ this.foldersAccordionExpanded = true;
9451
+ this.foldersAccordionExpandedChange.emit(true);
9452
+ }
9453
+ this.searchVisible = !this.searchVisible;
9454
+ if (!this.searchVisible && (this.searchValue || '').trim()) {
9455
+ this.onSearchClear();
9456
+ }
9457
+ this.cdr.markForCheck();
9458
+ }
9365
9459
  rowDropped(targetId, event) {
9366
9460
  // Directive already emits with the bound folder id, but normalise in case callers
9367
9461
  // wire raw events.
@@ -9513,10 +9607,10 @@ FolderSidebarComponent.RENAME_LIMIT_FLASH_MS = 600;
9513
9607
  * position is within this many px of the end. */
9514
9608
  FolderSidebarComponent.SCROLL_LOAD_THRESHOLD_PX = 64;
9515
9609
  FolderSidebarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: FolderSidebarComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
9516
- FolderSidebarComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: FolderSidebarComponent, selector: "cqa-folder-sidebar", inputs: { folders: "folders", selectedFolderId: "selectedFolderId", expandedFolderIds: "expandedFolderIds", unorganizedCount: "unorganizedCount", allowCreate: "allowCreate", allowRename: "allowRename", allowDelete: "allowDelete", allowMove: "allowMove", allowDuplicate: "allowDuplicate", allowDrop: "allowDrop", showCounts: "showCounts", collapsed: "collapsed", labels: "labels", serverSideSearch: "serverSideSearch", rootTotal: "rootTotal", folderSearchLoading: "folderSearchLoading", rootFoldersLoading: "rootFoldersLoading", searchValue: "searchValue" }, outputs: { folderSelected: "folderSelected", folderExpansionToggled: "folderExpansionToggled", folderChildrenRequested: "folderChildrenRequested", folderLoadMoreRequested: "folderLoadMoreRequested", searchChange: "searchChange", rootLoadMoreRequested: "rootLoadMoreRequested", folderCreated: "folderCreated", folderCreateRequested: "folderCreateRequested", folderRenamed: "folderRenamed", folderDeleted: "folderDeleted", folderMoveRequested: "folderMoveRequested", folderDuplicateRequested: "folderDuplicateRequested", testsDropped: "testsDropped", folderDropped: "folderDropped", collapsedChange: "collapsedChange" }, host: { listeners: { "document:click": "onDocumentClick()", "document:keydown.escape": "onEscape()", "document:dragend": "onDocumentDragEnd()" }, classAttribute: "cqa-ui-root" }, ngImport: i0, template: "<!-- Reusable folder icon. Render via <ng-container *ngTemplateOutlet=\"folderIcon; context: { color: node.color }\"></ng-container>. -->\n<ng-template #folderIcon let-color=\"color\">\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-4 cqa-h-4 cqa-shrink-0\">\n <svg width=\"13\" height=\"12\" viewBox=\"0 0 13 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.1916 9.85824C12.1916 10.1677 12.0687 10.4644 11.8499 10.6832C11.6311 10.902 11.3343 11.0249 11.0249 11.0249H1.69157C1.38215 11.0249 1.0854 10.902 0.866611 10.6832C0.647819 10.4644 0.524902 10.1677 0.524902 9.85824V1.69157C0.524902 1.38215 0.647819 1.0854 0.866611 0.866611C1.0854 0.647819 1.38215 0.524902 1.69157 0.524902H4.60824L5.7749 2.2749H11.0249C11.3343 2.2749 11.6311 2.39782 11.8499 2.61661C12.0687 2.8354 12.1916 3.13215 12.1916 3.44157V9.85824Z\" [attr.stroke]=\"color || '#99999E'\" stroke-width=\"1.05\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n</ng-template>\n\n<aside\n class=\"cqa-flex cqa-flex-col cqa-bg-white cqa-border cqa-border-neutral-200 cqa-rounded-lg cqa-h-full cqa-min-h-0\"\n [class.cqa-w-[240px]]=\"!collapsed\"\n [class.cqa-w-[48px]]=\"collapsed\"\n style=\"transition: width 150ms ease;\"\n>\n <!-- Header -->\n <div\n class=\"cqa-flex cqa-items-center cqa-justify-between cqa-px-3 cqa-py-1\"\n [style.border-bottom]=\"collapsed ? null : '1px solid #E2E2E3'\"\n [style.margin-bottom.px]=\"collapsed ? 0 : 8\"\n >\n <span *ngIf=\"!collapsed\" class=\"cqa-text-sm cqa-font-semibold cqa-text-neutral-900\">\n {{ labels.folders }}\n </span>\n <div class=\"cqa-flex cqa-items-center cqa-gap-1\" [class.cqa-w-full]=\"collapsed\" [class.cqa-justify-center]=\"collapsed\">\n <button\n type=\"button\"\n class=\"cqa-p-1 cqa-rounded hover:cqa-bg-neutral-100 cqa-text-neutral-500\"\n (click)=\"togglePanel()\"\n [attr.aria-label]=\"collapsed ? 'Expand folders panel' : 'Collapse folders panel'\"\n >\n <mat-icon style=\"font-size:18px;width:18px;height:18px\">\n {{ collapsed ? 'chevron_right' : 'keyboard_double_arrow_left' }}\n </mat-icon>\n </button>\n <button\n *ngIf=\"!collapsed && allowCreate\"\n type=\"button\"\n class=\"cqa-p-1 cqa-rounded hover:cqa-bg-neutral-100 cqa-text-neutral-500\"\n (click)=\"requestCreate(null)\"\n [attr.aria-label]=\"labels.newFolder\"\n [title]=\"labels.newFolder\"\n >\n <mat-icon style=\"font-size:18px;width:18px;height:18px\">add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container *ngIf=\"!collapsed\">\n <!-- Search -->\n <div class=\"cqa-px-3 cqa-pb-2\">\n <div class=\"cqa-relative\">\n <cqa-search-bar\n size=\"sm\"\n [fullWidth]=\"true\"\n [value]=\"searchValue\"\n [placeholder]=\"labels.searchFoldersPlaceholder\"\n [showClear]=\"true\"\n (valueChange)=\"onSearchInput($event)\"\n (cleared)=\"onSearchClear()\"\n ></cqa-search-bar>\n <span\n *ngIf=\"folderSearchLoading\"\n class=\"cqa-absolute cqa-right-8 cqa-inset-y-0 cqa-flex cqa-items-center\"\n aria-live=\"polite\"\n aria-label=\"Searching folders\"\n >\n <mat-spinner diameter=\"14\" mode=\"indeterminate\"></mat-spinner>\n </span>\n </div>\n </div>\n\n <!-- Tree (folders only \u2014 Unorganized sits directly below; tree caps at 60vh and scrolls when overflowing).\n Scroll-based lazy-loading at the root level: nearing the bottom emits\n `rootLoadMoreRequested`, which the host responds to by fetching the\n next page of roots. -->\n <div role=\"tree\" class=\"cqa-min-h-0 cqa-max-h-[60vh] cqa-overflow-y-auto cqa-py-1 cqa-scrollbar-thin cqa-scrollbar-track-transparent cqa-scrollbar-thumb-[#E5E7EB] cqa-scrollbar-thumb-rounded-full cqa-scrollbar-thumb-hover:cqa-bg-[#D1D5DB]\"\n (scroll)=\"onTreeScroll($event)\">\n <!-- Initial-load spinner: only renders when we have no folders yet AND\n no search active (search has its own inline spinner). -->\n <div\n *ngIf=\"rootFoldersLoading && folders.length === 0 && !searchValue?.trim()\"\n class=\"cqa-flex cqa-items-center cqa-justify-center cqa-py-6\"\n aria-live=\"polite\"\n >\n <mat-spinner diameter=\"20\" mode=\"indeterminate\"></mat-spinner>\n </div>\n <div\n *ngIf=\"searchValue?.trim() && rows.length === 0 && !folderSearchLoading\"\n class=\"cqa-px-3 cqa-py-2 cqa-text-sm cqa-text-neutral-500 cqa-text-center\"\n >\n {{ labels.noFoldersFound }}\n </div>\n <ng-container *ngFor=\"let row of rows; let i = index; trackBy: trackByRow\">\n <!-- Synthetic loading row (lazy fetch in flight) -->\n <div\n *ngIf=\"row.kind === 'loading'\"\n [style.paddingLeft.px]=\"8 + row.depth * 16\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-py-1.5 cqa-text-sm cqa-text-neutral-500\"\n aria-live=\"polite\"\n >\n <mat-spinner diameter=\"14\" mode=\"indeterminate\"></mat-spinner>\n <span>Loading\u2026</span>\n </div>\n <!-- Synthetic load-more row (more children available on backend) -->\n <button\n *ngIf=\"row.kind === 'load-more'\"\n type=\"button\"\n [style.paddingLeft.px]=\"8 + row.depth * 16\"\n class=\"cqa-flex cqa-items-center cqa-w-full cqa-py-1.5 cqa-text-sm cqa-text-indigo-600 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onLoadMore(row.node); $event.stopPropagation()\"\n >\n <span>Load more ({{ (row.node.totalChildren ?? 0) - (row.node.children?.length ?? 0) }})</span>\n </button>\n <!-- Real folder row -->\n <div\n *ngIf=\"row.kind === 'folder'\"\n role=\"treeitem\"\n tabindex=\"0\"\n [attr.aria-expanded]=\"row.hasChildren ? isExpanded(row.node.id) : null\"\n [attr.aria-selected]=\"isSelected(row.node.id)\"\n [attr.data-folder-row-id]=\"row.node.id\"\n [cqaFolderDrop]=\"row.node.id\"\n [dropEnabled]=\"allowDrop\"\n (testsDropped)=\"rowDropped(row.node.id, $event)\"\n (folderDropped)=\"onFolderRowDropped(row.node.id, $event)\"\n [cqaFolderDrag]=\"row.node.id\"\n [dragEnabled]=\"allowDrop\"\n (dragstart)=\"onFolderDragStart(row.node)\"\n (dragend)=\"onFolderDragEnd()\"\n (dragover)=\"onFolderRowDragOver(row)\"\n (dragleave)=\"onFolderRowDragLeave(row)\"\n (click)=\"onSelect(row.node)\"\n (contextmenu)=\"openContextMenu(row.node, $event)\"\n (keydown)=\"onRowKeydown($event, row, i)\"\n class=\"cqa-group cqa-flex cqa-items-center cqa-gap-1 cqa-pr-3 cqa-py-1.5 cqa-cursor-pointer hover:cqa-bg-indigo-50 focus:cqa-outline-none focus:cqa-bg-indigo-50 cqa-relative\"\n [class.cqa-bg-indigo-50]=\"isSelected(row.node.id) || contextMenuFolderId === row.node.id\"\n [style.paddingLeft.px]=\"8 + row.depth * 16\"\n [attr.title]=\"row.node.totalCount != null ? (row.node.totalCount + ' total including subfolders') : null\"\n >\n <span\n *ngIf=\"isSelected(row.node.id)\"\n class=\"cqa-absolute cqa-left-0 cqa-top-0 cqa-bottom-0 cqa-w-[3px] cqa-bg-indigo-600\"\n ></span>\n <button\n type=\"button\"\n class=\"cqa-p-0\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n [style.visibility]=\"row.hasChildren ? 'visible' : 'hidden'\"\n (click)=\"onToggle(row.node, $event)\"\n [attr.aria-label]=\"isExpanded(row.node.id) ? 'Collapse' : 'Expand'\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">\n {{ isExpanded(row.node.id) ? 'expand_more' : 'chevron_right' }}\n </mat-icon>\n </button>\n <ng-container *ngTemplateOutlet=\"folderIcon; context: { color: row.node.color }\"></ng-container>\n\n <ng-container *ngIf=\"renamingId === row.node.id; else nameTpl\">\n <input\n type=\"text\"\n size=\"1\"\n [attr.data-folder-rename-input]=\"row.node.id\"\n [attr.maxlength]=\"renameMaxLength\"\n class=\"cqa-flex-1 cqa-min-w-0 cqa-w-full cqa-px-2 cqa-py-0.5 cqa-rounded cqa-border cqa-bg-white cqa-text-sm cqa-shadow-sm focus:cqa-outline-none focus:cqa-ring-1\"\n [ngClass]=\"(isRenameDraftValid && !renameLimitFlash)\n ? 'cqa-border-neutral-300 focus:cqa-border-indigo-400 focus:cqa-ring-indigo-200'\n : 'cqa-border-[#EF4444] focus:cqa-border-[#EF4444] focus:cqa-ring-[#FCA5A5]'\"\n [attr.aria-invalid]=\"!isRenameDraftValid || renameLimitFlash\"\n [(ngModel)]=\"renameDraft\"\n (keydown)=\"onRenameKey($event, row.node)\"\n (keypress)=\"$event.stopPropagation()\"\n (keyup)=\"$event.stopPropagation()\"\n (blur)=\"commitRename(row.node)\"\n (click)=\"$event.stopPropagation()\"\n />\n </ng-container>\n <ng-template #nameTpl>\n <span\n class=\"cqa-flex-1 cqa-text-sm cqa-truncate\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n (dblclick)=\"beginRename(row.node, $event)\"\n >{{ row.node.name }}</span>\n </ng-template>\n\n <!-- Count shown at rest; hidden on row hover when delete is allowed -->\n <span\n *ngIf=\"showCounts && row.node.count != null\"\n class=\"cqa-text-xs cqa-tabular-nums cqa-ml-1\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n [class.group-hover:cqa-hidden]=\"allowDelete\"\n >{{ row.node.count }}</span>\n\n <button\n *ngIf=\"hasAnyContextAction\"\n type=\"button\"\n class=\"cqa-p-0.5 cqa-rounded hover:cqa-bg-neutral-200\"\n [style.color]=\"isSelected(row.node.id) || contextMenuFolderId === row.node.id ? '#3F43EE' : '#4C4C51'\"\n [class.cqa-hidden]=\"contextMenuFolderId !== row.node.id\"\n [class.group-hover:cqa-inline-flex]=\"true\"\n [class.cqa-inline-flex]=\"contextMenuFolderId === row.node.id\"\n (click)=\"openContextMenu(row.node, $event)\"\n [attr.aria-label]=\"'Open actions for ' + row.node.name\"\n [attr.aria-haspopup]=\"'menu'\"\n [attr.aria-expanded]=\"contextMenuFolderId === row.node.id\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">more_horiz</mat-icon>\n </button>\n </div>\n </ng-container>\n <!-- Root-level pagination is now driven by scroll (see `(scroll)` on the\n tree container above). Per-folder children retain the explicit\n \"Load more\" button rendered inside the rows loop above, since\n subtrees aren't independently scrollable. -->\n <!-- Pagination spinner \u2014 visible only when fetching an additional page\n of roots (folders.length > 0 distinguishes from initial-load case). -->\n <div\n *ngIf=\"rootFoldersLoading && folders.length > 0 && !searchValue?.trim()\"\n class=\"cqa-flex cqa-items-center cqa-justify-center cqa-py-2\"\n aria-live=\"polite\"\n >\n <mat-spinner diameter=\"14\" mode=\"indeterminate\"></mat-spinner>\n </div>\n </div>\n\n <!-- Divider between folder tree and pinned Unorganized tab -->\n <div\n aria-hidden=\"true\"\n class=\"cqa-mx-3 cqa-shrink-0\"\n style=\"height: 1px; background-color: #E5E7EB; margin-top: 6px; margin-bottom: 6px;\"\n ></div>\n\n <!-- Unorganized \u2014 pinned below the scrollable folder tree so it stays visible regardless of folder count -->\n <div\n role=\"treeitem\"\n tabindex=\"0\"\n [attr.aria-selected]=\"isSelected(null)\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-px-3 cqa-py-1.5 cqa-cursor-pointer hover:cqa-bg-indigo-50 focus:cqa-outline-none focus:cqa-bg-indigo-50 cqa-relative cqa-shrink-0\"\n [class.cqa-bg-indigo-50]=\"isSelected(null)\"\n [cqaFolderDrop]=\"null\"\n [dropEnabled]=\"allowDrop\"\n (testsDropped)=\"rowDropped(null, $event)\"\n (folderDropped)=\"onFolderRowDropped(null, $event)\"\n (click)=\"onSelectUnorganized()\"\n >\n <span\n *ngIf=\"isSelected(null)\"\n class=\"cqa-absolute cqa-left-0 cqa-top-0 cqa-bottom-0 cqa-w-[3px] cqa-bg-indigo-600\"\n ></span>\n <mat-icon [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\" style=\"font-size:16px;width:16px;height:16px\">inbox</mat-icon>\n <span class=\"cqa-flex-1 cqa-text-sm\" [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\">{{ labels.unorganized }}</span>\n <span *ngIf=\"showCounts\" class=\"cqa-text-xs cqa-tabular-nums\" [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\">{{ unorganizedCount }}</span>\n </div>\n\n <!-- Folder context menu (right-click / ellipsis). A 0\u00D70 fixed-position\n anchor at the click coords drives a cdkConnectedOverlay; CDK's\n flexible-connected positioning auto-flips up/left when the menu\n would overflow the viewport (last-folder-near-bottom case). -->\n <div #ctxAnchor cdkOverlayOrigin\n class=\"cqa-fixed cqa-w-0 cqa-h-0 cqa-pointer-events-none\"\n [style.left.px]=\"contextMenuPosition.x\"\n [style.top.px]=\"contextMenuPosition.y\"></div>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"ctxAnchor\"\n [cdkConnectedOverlayOpen]=\"contextMenuFolderId !== null\"\n [cdkConnectedOverlayPositions]=\"contextMenuPositions\"\n [cdkConnectedOverlayHasBackdrop]=\"false\"\n [cdkConnectedOverlayFlexibleDimensions]=\"false\"\n [cdkConnectedOverlayPush]=\"true\"\n (overlayOutsideClick)=\"onOverlayOutsideClick()\"\n (detach)=\"closeContextMenu()\"\n >\n <!-- The lib's tailwind config sets important: '.cqa-ui-root', which\n emits descendant selectors like `.cqa-ui-root .cqa-bg-white`.\n CDK Overlay teleports this template into the global overlay\n container (outside the host's cqa-ui-root), so we wrap the menu in\n an ancestor cqa-ui-root div to re-establish the utility scope. -->\n <div class=\"cqa-ui-root\">\n <div\n role=\"menu\"\n class=\"cqa-min-w-[180px] cqa-bg-white cqa-border cqa-border-neutral-200 cqa-rounded-md cqa-shadow-lg cqa-py-1\"\n (click)=\"$event.stopPropagation()\"\n (contextmenu)=\"$event.preventDefault(); $event.stopPropagation()\"\n >\n <ng-container *ngIf=\"folderById(contextMenuFolderId) as menuNode\">\n <button\n *ngIf=\"allowCreate\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextCreateSubfolder(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">create_new_folder</mat-icon>\n <span>{{ labels.folderMenuCreateSubfolder }}</span>\n </button>\n <button\n *ngIf=\"allowRename\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextRename(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">edit</mat-icon>\n <span>{{ labels.folderMenuRename }}</span>\n </button>\n <button\n *ngIf=\"allowMove\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextMove(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">drive_file_move</mat-icon>\n <span>{{ labels.folderMenuMove }}</span>\n </button>\n <button\n *ngIf=\"allowDuplicate\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextDuplicate(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">content_copy</mat-icon>\n <span>{{ labels.folderMenuDuplicate }}</span>\n </button>\n <div *ngIf=\"allowDelete && (allowCreate || allowRename || allowMove || allowDuplicate)\" class=\"cqa-h-px cqa-bg-neutral-200 cqa-my-1\"></div>\n <button\n *ngIf=\"allowDelete\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-red-600 hover:cqa-bg-red-50 cqa-text-left\"\n (click)=\"onContextDelete(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">delete_outline</mat-icon>\n <span>{{ labels.folderMenuDelete }}</span>\n </button>\n </ng-container>\n </div>\n </div>\n </ng-template>\n\n </ng-container>\n</aside>\n", components: [{ type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { type: SearchBarComponent, selector: "cqa-search-bar", inputs: ["placeholder", "value", "disabled", "showClear", "ariaLabel", "autoFocus", "size", "fullWidth"], outputs: ["valueChange", "search", "cleared"] }, { type: i4.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "diameter", "strokeWidth", "mode", "value"], exportAs: ["matProgressSpinner"] }], directives: [{ type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: FolderDropDirective, selector: "[cqaFolderDrop]", inputs: ["cqaFolderDrop", "dropEnabled"], outputs: ["testsDropped", "folderDropped"] }, { type: FolderDragDirective, selector: "[cqaFolderDrag]", inputs: ["cqaFolderDrag", "dragEnabled"] }, { type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }, { type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { type: i1$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { type: i1$6.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { type: i1$6.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9610
+ FolderSidebarComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: FolderSidebarComponent, selector: "cqa-folder-sidebar", inputs: { folders: "folders", selectedFolderId: "selectedFolderId", expandedFolderIds: "expandedFolderIds", unorganizedCount: "unorganizedCount", allowCreate: "allowCreate", allowRename: "allowRename", allowDelete: "allowDelete", allowMove: "allowMove", allowDuplicate: "allowDuplicate", allowDrop: "allowDrop", showCounts: "showCounts", collapsed: "collapsed", foldersAccordionExpanded: "foldersAccordionExpanded", labels: "labels", serverSideSearch: "serverSideSearch", rootTotal: "rootTotal", folderSearchLoading: "folderSearchLoading", rootFoldersLoading: "rootFoldersLoading", savingFolderIds: "savingFolderIds", searchValue: "searchValue" }, outputs: { folderSelected: "folderSelected", folderExpansionToggled: "folderExpansionToggled", folderChildrenRequested: "folderChildrenRequested", folderLoadMoreRequested: "folderLoadMoreRequested", searchChange: "searchChange", rootLoadMoreRequested: "rootLoadMoreRequested", folderCreated: "folderCreated", folderCreateRequested: "folderCreateRequested", folderRenamed: "folderRenamed", folderDeleted: "folderDeleted", folderMoveRequested: "folderMoveRequested", folderDuplicateRequested: "folderDuplicateRequested", testsDropped: "testsDropped", folderDropped: "folderDropped", collapsedChange: "collapsedChange", foldersAccordionExpandedChange: "foldersAccordionExpandedChange" }, host: { listeners: { "document:click": "onDocumentClick()", "document:keydown.escape": "onEscape()", "document:dragend": "onDocumentDragEnd()" }, classAttribute: "cqa-ui-root" }, ngImport: i0, template: "<!-- Reusable indeterminate loading spinner. Render via\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 14 }\"></ng-container>. -->\n<ng-template #loadingSpinner let-size=\"size\">\n <svg [attr.width]=\"size || 16\" [attr.height]=\"size || 16\" viewBox=\"0 0 50 50\" aria-label=\"loading\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"25\" cy=\"25\" r=\"20\" stroke=\"#E5E7EB\" stroke-width=\"6\" fill=\"none\"></circle>\n <path d=\"M45 25a20 20 0 0 0-20-20\" stroke=\"#4F46E5\" stroke-width=\"6\" fill=\"none\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 25 25\" to=\"360 25 25\" dur=\"0.8s\" repeatCount=\"indefinite\"></animateTransform>\n </path>\n </svg>\n</ng-template>\n\n<!-- Reusable folder icon. Render via <ng-container *ngTemplateOutlet=\"folderIcon; context: { color: node.color }\"></ng-container>. -->\n<ng-template #folderIcon let-color=\"color\">\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-4 cqa-h-4 cqa-flex-shrink-0\">\n <svg width=\"13\" height=\"12\" viewBox=\"0 0 13 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.1916 9.85824C12.1916 10.1677 12.0687 10.4644 11.8499 10.6832C11.6311 10.902 11.3343 11.0249 11.0249 11.0249H1.69157C1.38215 11.0249 1.0854 10.902 0.866611 10.6832C0.647819 10.4644 0.524902 10.1677 0.524902 9.85824V1.69157C0.524902 1.38215 0.647819 1.0854 0.866611 0.866611C1.0854 0.647819 1.38215 0.524902 1.69157 0.524902H4.60824L5.7749 2.2749H11.0249C11.3343 2.2749 11.6311 2.39782 11.8499 2.61661C12.0687 2.8354 12.1916 3.13215 12.1916 3.44157V9.85824Z\" [attr.stroke]=\"color || '#99999E'\" stroke-width=\"1.05\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n</ng-template>\n\n<aside\n class=\"cqa-flex cqa-flex-col cqa-bg-white cqa-h-full cqa-min-h-0\"\n [class.cqa-border]=\"!collapsed\"\n [class.cqa-border-neutral-200]=\"!collapsed\"\n [class.cqa-rounded-lg]=\"!collapsed\"\n [style.width.px]=\"collapsed ? 22 : 240\"\n style=\"overflow: hidden; transition: width 260ms cubic-bezier(0.4, 0, 0.2, 1);\"\n>\n <!-- Collapsed rail: the entire sidebar becomes a single click target so the\n slim strip reads as an obvious \"expand me\" handle. -->\n <button\n *ngIf=\"collapsed\"\n type=\"button\"\n style=\"width: 22px;\"\n class=\"cqa-group cqa-flex cqa-flex-col cqa-items-center cqa-flex-shrink-0 cqa-h-full cqa-py-2 cqa-gap-3 cqa-bg-neutral-50 cqa-border cqa-border-neutral-200 cqa-text-neutral-500 hover:cqa-bg-indigo-50 hover:cqa-border-indigo-200 hover:cqa-text-indigo-600 focus:cqa-outline-none focus:cqa-ring-2 focus:cqa-ring-indigo-200 cqa-cursor-pointer\"\n (click)=\"togglePanel()\"\n [attr.aria-label]=\"'Expand folders panel'\"\n [title]=\"'Expand folders panel'\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">chevron_right</mat-icon>\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center\" aria-hidden=\"true\">\n <svg width=\"13\" height=\"12\" viewBox=\"0 0 13 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M12.1916 9.85824C12.1916 10.1677 12.0687 10.4644 11.8499 10.6832C11.6311 10.902 11.3343 11.0249 11.0249 11.0249H1.69157C1.38215 11.0249 1.0854 10.902 0.866611 10.6832C0.647819 10.4644 0.524902 10.1677 0.524902 9.85824V1.69157C0.524902 1.38215 0.647819 1.0854 0.866611 0.866611C1.0854 0.647819 1.38215 0.524902 1.69157 0.524902H4.60824L5.7749 2.2749H11.0249C11.3343 2.2749 11.6311 2.39782 11.8499 2.61661C12.0687 2.8354 12.1916 3.13215 12.1916 3.44157V9.85824Z\" stroke=\"currentColor\" stroke-width=\"1.05\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </button>\n\n <div *ngIf=\"!collapsed\" class=\"cqa-flex cqa-flex-col cqa-h-full cqa-min-h-0 cqa-pb-2\" style=\"width: 240px;\">\n <!-- Header (expanded) -->\n <div\n class=\"cqa-flex cqa-items-center cqa-justify-between cqa-py-1 cqa-px-3\"\n style=\"border-bottom: 1px solid #E2E2E3; margin-bottom: 8px;\"\n >\n <span class=\"cqa-text-sm cqa-font-semibold cqa-text-neutral-900 cqa-py-0.5\">{{ labels.folders }}</span>\n <div class=\"cqa-flex cqa-items-center cqa-gap-1\">\n <button\n type=\"button\"\n class=\"cqa-p-1 cqa-rounded hover:cqa-bg-neutral-100 cqa-text-neutral-500\"\n (click)=\"togglePanel()\"\n [attr.aria-label]=\"'Collapse folders panel'\"\n [title]=\"'Collapse folders panel'\"\n >\n <mat-icon style=\"font-size:18px;width:18px;height:18px\">keyboard_double_arrow_left</mat-icon>\n </button>\n <button\n type=\"button\"\n class=\"cqa-p-1 cqa-rounded hover:cqa-bg-neutral-100\"\n [class.cqa-text-neutral-500]=\"!searchVisible\"\n [class.cqa-text-indigo-600]=\"searchVisible\"\n [class.cqa-bg-neutral-100]=\"searchVisible\"\n (click)=\"toggleSearchVisible()\"\n [attr.aria-label]=\"searchVisible ? 'Hide folder search' : 'Search folders'\"\n [attr.aria-pressed]=\"searchVisible\"\n [title]=\"searchVisible ? 'Hide folder search' : 'Search folders'\"\n >\n <mat-icon style=\"font-size:18px;width:18px;height:18px\">search</mat-icon>\n </button>\n <button\n *ngIf=\"allowCreate\"\n type=\"button\"\n class=\"cqa-p-1 cqa-rounded hover:cqa-bg-neutral-100 cqa-text-neutral-500\"\n (click)=\"requestCreate(null)\"\n [attr.aria-label]=\"labels.newFolder\"\n [title]=\"labels.newFolder\"\n >\n <mat-icon style=\"font-size:18px;width:18px;height:18px\">add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container>\n <!-- Search (toggled via the search icon in the header) -->\n <div\n class=\"cqa-grid\"\n style=\"transition: grid-template-rows 300ms cubic-bezier(0.4, 0, 0.2, 1);\"\n [style.grid-template-rows]=\"searchVisible ? '1fr' : '0fr'\"\n >\n <div class=\"cqa-min-h-0\" style=\"overflow: hidden;\">\n <div class=\"cqa-px-3 cqa-pb-2\">\n <div class=\"cqa-relative\">\n <cqa-search-bar\n size=\"sm\"\n [fullWidth]=\"true\"\n [value]=\"searchValue\"\n [autoFocus]=\"searchVisible\"\n [placeholder]=\"labels.searchFoldersPlaceholder\"\n [showClear]=\"true\"\n (valueChange)=\"onSearchInput($event)\"\n (cleared)=\"onSearchClear()\"\n ></cqa-search-bar>\n <span\n *ngIf=\"folderSearchLoading\"\n class=\"cqa-absolute cqa-right-8 cqa-inset-y-0 cqa-flex cqa-items-center\"\n aria-live=\"polite\"\n aria-label=\"Searching folders\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 14 }\"></ng-container>\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Organized section header \u2014 visually distinct from folder rows so it reads\n as a view/section toggle rather than a folder item. -->\n <button\n type=\"button\"\n class=\"cqa-flex cqa-items-center cqa-justify-between cqa-w-full cqa-px-3 cqa-py-1 cqa-text-left cqa-text-[#6D6D74] hover:cqa-text-[#3F43EE] focus:cqa-outline-none focus:cqa-text-[#3F43EE] cqa-flex-shrink-0 cqa-uppercase\"\n (click)=\"toggleFoldersAccordion()\"\n [attr.aria-expanded]=\"foldersAccordionExpanded\"\n [attr.aria-label]=\"foldersAccordionExpanded ? 'Collapse organized folders' : 'Expand organized folders'\"\n >\n <span class=\"cqa-text-[11px] cqa-font-semibold cqa-tracking-wider\">{{ labels.organized }}</span>\n <mat-icon\n style=\"font-size:16px;width:16px;height:16px;transition: transform 200ms ease;\"\n [style.transform]=\"foldersAccordionExpanded ? 'rotate(0deg)' : 'rotate(-90deg)'\"\n >expand_more</mat-icon>\n </button>\n\n <!-- Tree (folders only \u2014 Unorganized sits directly below; tree caps at 60vh and scrolls when overflowing).\n Scroll-based lazy-loading at the root level: nearing the bottom emits\n `rootLoadMoreRequested`, which the host responds to by fetching the\n next page of roots. -->\n <div\n class=\"cqa-grid cqa-min-h-0\"\n style=\"transition: grid-template-rows 300ms cubic-bezier(0.4, 0, 0.2, 1);\"\n [style.grid-template-rows]=\"foldersAccordionExpanded ? '1fr' : '0fr'\"\n >\n <div class=\"cqa-min-h-0\" style=\"overflow: hidden;\">\n <div role=\"tree\" class=\"cqa-min-h-0 cqa-max-h-[40vh] cqa-overflow-y-auto cqa-py-1 cqa-scrollbar-thin cqa-scrollbar-track-transparent cqa-scrollbar-thumb-[#E5E7EB] cqa-scrollbar-thumb-rounded-full cqa-scrollbar-thumb-hover:cqa-bg-[#D1D5DB]\"\n (scroll)=\"onTreeScroll($event)\">\n <!-- Initial-load spinner: only renders when we have no folders yet AND\n no search active (search has its own inline spinner). -->\n <div\n *ngIf=\"rootFoldersLoading && folders.length === 0 && !searchValue?.trim()\"\n class=\"cqa-flex cqa-items-center cqa-justify-center cqa-py-6\"\n aria-live=\"polite\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 20 }\"></ng-container>\n </div>\n <div\n *ngIf=\"searchValue?.trim() && rows.length === 0 && !folderSearchLoading\"\n class=\"cqa-px-3 cqa-py-2 cqa-text-sm cqa-text-neutral-500 cqa-text-center\"\n >\n {{ labels.noFoldersFound }}\n </div>\n <ng-container *ngFor=\"let row of rows; let i = index; trackBy: trackByRow\">\n <!-- Synthetic loading row (lazy fetch in flight) -->\n <div\n *ngIf=\"row.kind === 'loading'\"\n [style.paddingLeft.px]=\"rowIndent(row.depth)\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-py-1.5 cqa-text-sm cqa-text-neutral-500\"\n aria-live=\"polite\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 14 }\"></ng-container>\n <span>Loading\u2026</span>\n </div>\n <!-- Synthetic load-more row (more children available on backend) -->\n <button\n *ngIf=\"row.kind === 'load-more'\"\n type=\"button\"\n [style.paddingLeft.px]=\"rowIndent(row.depth)\"\n class=\"cqa-flex cqa-items-center cqa-w-full cqa-py-1.5 cqa-text-sm cqa-text-indigo-600 hover:cqa-bg-[#D8D9FC] cqa-text-left\"\n (click)=\"onLoadMore(row.node); $event.stopPropagation()\"\n >\n <span>Load more ({{ (row.node.totalChildren ?? 0) - (row.node.children?.length ?? 0) }})</span>\n </button>\n <!-- Real folder row -->\n <div\n *ngIf=\"row.kind === 'folder'\"\n role=\"treeitem\"\n tabindex=\"0\"\n [attr.aria-expanded]=\"row.hasChildren ? isExpanded(row.node.id) : null\"\n [attr.aria-selected]=\"isSelected(row.node.id)\"\n [attr.data-folder-row-id]=\"row.node.id\"\n [cqaFolderDrop]=\"row.node.id\"\n [dropEnabled]=\"allowDrop\"\n (testsDropped)=\"rowDropped(row.node.id, $event)\"\n (folderDropped)=\"onFolderRowDropped(row.node.id, $event)\"\n [cqaFolderDrag]=\"row.node.id\"\n [dragEnabled]=\"allowDrop\"\n (dragstart)=\"onFolderDragStart(row.node)\"\n (dragend)=\"onFolderDragEnd()\"\n (dragover)=\"onFolderRowDragOver(row)\"\n (dragleave)=\"onFolderRowDragLeave(row)\"\n (click)=\"onSelect(row.node)\"\n (contextmenu)=\"openContextMenu(row.node, $event)\"\n (keydown)=\"onRowKeydown($event, row, i)\"\n class=\"cqa-group cqa-flex cqa-items-center cqa-gap-1 cqa-pr-3 cqa-py-1.5 cqa-cursor-pointer hover:cqa-bg-[#D8D9FC] focus:cqa-outline-none focus:cqa-bg-[#D8D9FC] cqa-relative\"\n [style.background-color]=\"(isSelected(row.node.id) || contextMenuFolderId === row.node.id) ? '#D8D9FC' : null\"\n [style.paddingLeft.px]=\"rowIndent(row.depth)\"\n [attr.title]=\"folderRowTooltip(row.node)\"\n >\n <span\n *ngIf=\"isSelected(row.node.id)\"\n class=\"cqa-absolute cqa-left-0 cqa-top-0 cqa-bottom-0 cqa-w-[3px] cqa-bg-indigo-600\"\n ></span>\n <button\n type=\"button\"\n class=\"cqa-p-0\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n [style.visibility]=\"row.hasChildren ? 'visible' : 'hidden'\"\n (click)=\"onToggle(row.node, $event)\"\n [attr.aria-label]=\"isExpanded(row.node.id) ? 'Collapse' : 'Expand'\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">\n {{ isExpanded(row.node.id) ? 'expand_more' : 'chevron_right' }}\n </mat-icon>\n </button>\n <ng-container *ngTemplateOutlet=\"folderIcon; context: { color: row.node.color }\"></ng-container>\n\n <ng-container *ngIf=\"renamingId === row.node.id; else nameTpl\">\n <input\n type=\"text\"\n size=\"1\"\n [attr.data-folder-rename-input]=\"row.node.id\"\n [attr.maxlength]=\"renameMaxLength\"\n class=\"cqa-flex-1 cqa-min-w-0 cqa-w-full cqa-px-2 cqa-py-0.5 cqa-rounded cqa-border cqa-bg-white cqa-text-sm cqa-shadow-sm focus:cqa-outline-none focus:cqa-ring-1\"\n [ngClass]=\"(isRenameDraftValid && !renameLimitFlash)\n ? 'cqa-border-neutral-300 focus:cqa-border-indigo-400 focus:cqa-ring-indigo-200'\n : 'cqa-border-[#EF4444] focus:cqa-border-[#EF4444] focus:cqa-ring-[#FCA5A5]'\"\n [attr.aria-invalid]=\"!isRenameDraftValid || renameLimitFlash\"\n [(ngModel)]=\"renameDraft\"\n (keydown)=\"onRenameKey($event, row.node)\"\n (keypress)=\"$event.stopPropagation()\"\n (keyup)=\"$event.stopPropagation()\"\n (blur)=\"commitRename(row.node)\"\n (click)=\"$event.stopPropagation()\"\n />\n </ng-container>\n <ng-template #nameTpl>\n <span\n class=\"cqa-flex-1 cqa-text-sm cqa-truncate\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n (dblclick)=\"beginRename(row.node, $event)\"\n >{{ row.node.name }}</span>\n </ng-template>\n\n <!-- Saving indicator: replaces the count while the host's rename/save\n API call is in flight, so the user gets immediate feedback. -->\n <span\n *ngIf=\"isFolderSaving(row.node.id)\"\n class=\"cqa-flex cqa-items-center cqa-ml-1 cqa-flex-shrink-0\"\n aria-live=\"polite\"\n [attr.aria-label]=\"'Saving ' + row.node.name\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 12 }\"></ng-container>\n </span>\n\n <!-- Count shown at rest; hidden on row hover when delete is allowed -->\n <span\n *ngIf=\"!isFolderSaving(row.node.id) && showCounts && row.node.count != null\"\n class=\"cqa-text-xs cqa-tabular-nums cqa-ml-1\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n [class.group-hover:cqa-hidden]=\"allowDelete\"\n >{{ row.node.count }}</span>\n\n <button\n *ngIf=\"hasAnyContextAction\"\n type=\"button\"\n class=\"cqa-p-0.5 cqa-rounded hover:cqa-bg-neutral-200\"\n [style.color]=\"isSelected(row.node.id) || contextMenuFolderId === row.node.id ? '#3F43EE' : '#4C4C51'\"\n [class.cqa-hidden]=\"contextMenuFolderId !== row.node.id\"\n [class.group-hover:cqa-inline-flex]=\"true\"\n [class.cqa-inline-flex]=\"contextMenuFolderId === row.node.id\"\n (click)=\"openContextMenu(row.node, $event)\"\n [attr.aria-label]=\"'Open actions for ' + row.node.name\"\n [attr.aria-haspopup]=\"'menu'\"\n [attr.aria-expanded]=\"contextMenuFolderId === row.node.id\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">more_horiz</mat-icon>\n </button>\n </div>\n </ng-container>\n <!-- Root-level pagination is now driven by scroll (see `(scroll)` on the\n tree container above). Per-folder children retain the explicit\n \"Load more\" button rendered inside the rows loop above, since\n subtrees aren't independently scrollable. -->\n <!-- Pagination spinner \u2014 visible only when fetching an additional page\n of roots (folders.length > 0 distinguishes from initial-load case). -->\n <div\n *ngIf=\"rootFoldersLoading && folders.length > 0 && !searchValue?.trim()\"\n class=\"cqa-flex cqa-items-center cqa-justify-center cqa-py-2\"\n aria-live=\"polite\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 14 }\"></ng-container>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Divider between folder tree and pinned Unorganized tab -->\n <div\n aria-hidden=\"true\"\n class=\"cqa-mx-3 cqa-flex-shrink-0\"\n style=\"height: 1px; background-color: #E5E7EB; margin-top: 6px; margin-bottom: 6px;\"\n ></div>\n\n <!-- Unorganized \u2014 pinned below the scrollable folder tree so it stays visible regardless of folder count -->\n <div\n role=\"treeitem\"\n tabindex=\"0\"\n [attr.aria-selected]=\"isSelected(null)\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-px-3 cqa-py-1.5 cqa-cursor-pointer hover:cqa-bg-[#D8D9FC] focus:cqa-outline-none focus:cqa-bg-[#D8D9FC] cqa-relative cqa-flex-shrink-0\"\n [style.background-color]=\"isSelected(null) ? '#D8D9FC' : null\"\n [cqaFolderDrop]=\"null\"\n [dropEnabled]=\"allowDrop\"\n (testsDropped)=\"rowDropped(null, $event)\"\n (folderDropped)=\"onFolderRowDropped(null, $event)\"\n (click)=\"onSelectUnorganized()\"\n >\n <span\n *ngIf=\"isSelected(null)\"\n class=\"cqa-absolute cqa-left-0 cqa-top-0 cqa-bottom-0 cqa-w-[3px] cqa-bg-indigo-600\"\n ></span>\n <mat-icon [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\" style=\"font-size:16px;width:16px;height:16px\">inbox</mat-icon>\n <span class=\"cqa-flex-1 cqa-text-sm\" [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\">{{ labels.unorganized }}</span>\n <span *ngIf=\"showCounts\" class=\"cqa-text-xs cqa-tabular-nums\" [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\">{{ unorganizedCount }}</span>\n </div>\n\n <!-- Folder context menu (right-click / ellipsis). A 0\u00D70 fixed-position\n anchor at the click coords drives a cdkConnectedOverlay; CDK's\n flexible-connected positioning auto-flips up/left when the menu\n would overflow the viewport (last-folder-near-bottom case). -->\n <div #ctxAnchor cdkOverlayOrigin\n class=\"cqa-fixed cqa-w-0 cqa-h-0 cqa-pointer-events-none\"\n [style.left.px]=\"contextMenuPosition.x\"\n [style.top.px]=\"contextMenuPosition.y\"></div>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"ctxAnchor\"\n [cdkConnectedOverlayOpen]=\"contextMenuFolderId !== null\"\n [cdkConnectedOverlayPositions]=\"contextMenuPositions\"\n [cdkConnectedOverlayHasBackdrop]=\"false\"\n [cdkConnectedOverlayFlexibleDimensions]=\"false\"\n [cdkConnectedOverlayPush]=\"true\"\n (overlayOutsideClick)=\"onOverlayOutsideClick()\"\n (detach)=\"closeContextMenu()\"\n >\n <!-- The lib's tailwind config sets important: '.cqa-ui-root', which\n emits descendant selectors like `.cqa-ui-root .cqa-bg-white`.\n CDK Overlay teleports this template into the global overlay\n container (outside the host's cqa-ui-root), so we wrap the menu in\n an ancestor cqa-ui-root div to re-establish the utility scope. -->\n <div class=\"cqa-ui-root\">\n <div\n role=\"menu\"\n class=\"cqa-min-w-[180px] cqa-bg-white cqa-border cqa-border-neutral-200 cqa-rounded-md cqa-shadow-lg cqa-py-1\"\n (click)=\"$event.stopPropagation()\"\n (contextmenu)=\"$event.preventDefault(); $event.stopPropagation()\"\n >\n <ng-container *ngIf=\"folderById(contextMenuFolderId) as menuNode\">\n <button\n *ngIf=\"allowCreate\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextCreateSubfolder(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">create_new_folder</mat-icon>\n <span>{{ labels.folderMenuCreateSubfolder }}</span>\n </button>\n <button\n *ngIf=\"allowRename\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextRename(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">edit</mat-icon>\n <span>{{ labels.folderMenuRename }}</span>\n </button>\n <button\n *ngIf=\"allowMove\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextMove(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">drive_file_move</mat-icon>\n <span>{{ labels.folderMenuMove }}</span>\n </button>\n <button\n *ngIf=\"allowDuplicate\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextDuplicate(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">content_copy</mat-icon>\n <span>{{ labels.folderMenuDuplicate }}</span>\n </button>\n <div *ngIf=\"allowDelete && (allowCreate || allowRename || allowMove || allowDuplicate)\" class=\"cqa-h-px cqa-bg-neutral-200 cqa-my-1\"></div>\n <button\n *ngIf=\"allowDelete\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-red-600 hover:cqa-bg-red-50 cqa-text-left\"\n (click)=\"onContextDelete(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">delete_outline</mat-icon>\n <span>{{ labels.folderMenuDelete }}</span>\n </button>\n </ng-container>\n </div>\n </div>\n </ng-template>\n\n </ng-container>\n </div>\n</aside>\n", components: [{ type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { type: SearchBarComponent, selector: "cqa-search-bar", inputs: ["placeholder", "value", "disabled", "showClear", "ariaLabel", "autoFocus", "size", "fullWidth"], outputs: ["valueChange", "search", "cleared"] }], directives: [{ type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }, { type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: FolderDropDirective, selector: "[cqaFolderDrop]", inputs: ["cqaFolderDrop", "dropEnabled"], outputs: ["testsDropped", "folderDropped"] }, { type: FolderDragDirective, selector: "[cqaFolderDrag]", inputs: ["cqaFolderDrag", "dragEnabled"] }, { type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { type: i1$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { type: i1$6.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { type: i1$6.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9517
9611
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: FolderSidebarComponent, decorators: [{
9518
9612
  type: Component,
9519
- args: [{ selector: 'cqa-folder-sidebar', host: { class: 'cqa-ui-root' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Reusable folder icon. Render via <ng-container *ngTemplateOutlet=\"folderIcon; context: { color: node.color }\"></ng-container>. -->\n<ng-template #folderIcon let-color=\"color\">\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-4 cqa-h-4 cqa-shrink-0\">\n <svg width=\"13\" height=\"12\" viewBox=\"0 0 13 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.1916 9.85824C12.1916 10.1677 12.0687 10.4644 11.8499 10.6832C11.6311 10.902 11.3343 11.0249 11.0249 11.0249H1.69157C1.38215 11.0249 1.0854 10.902 0.866611 10.6832C0.647819 10.4644 0.524902 10.1677 0.524902 9.85824V1.69157C0.524902 1.38215 0.647819 1.0854 0.866611 0.866611C1.0854 0.647819 1.38215 0.524902 1.69157 0.524902H4.60824L5.7749 2.2749H11.0249C11.3343 2.2749 11.6311 2.39782 11.8499 2.61661C12.0687 2.8354 12.1916 3.13215 12.1916 3.44157V9.85824Z\" [attr.stroke]=\"color || '#99999E'\" stroke-width=\"1.05\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n</ng-template>\n\n<aside\n class=\"cqa-flex cqa-flex-col cqa-bg-white cqa-border cqa-border-neutral-200 cqa-rounded-lg cqa-h-full cqa-min-h-0\"\n [class.cqa-w-[240px]]=\"!collapsed\"\n [class.cqa-w-[48px]]=\"collapsed\"\n style=\"transition: width 150ms ease;\"\n>\n <!-- Header -->\n <div\n class=\"cqa-flex cqa-items-center cqa-justify-between cqa-px-3 cqa-py-1\"\n [style.border-bottom]=\"collapsed ? null : '1px solid #E2E2E3'\"\n [style.margin-bottom.px]=\"collapsed ? 0 : 8\"\n >\n <span *ngIf=\"!collapsed\" class=\"cqa-text-sm cqa-font-semibold cqa-text-neutral-900\">\n {{ labels.folders }}\n </span>\n <div class=\"cqa-flex cqa-items-center cqa-gap-1\" [class.cqa-w-full]=\"collapsed\" [class.cqa-justify-center]=\"collapsed\">\n <button\n type=\"button\"\n class=\"cqa-p-1 cqa-rounded hover:cqa-bg-neutral-100 cqa-text-neutral-500\"\n (click)=\"togglePanel()\"\n [attr.aria-label]=\"collapsed ? 'Expand folders panel' : 'Collapse folders panel'\"\n >\n <mat-icon style=\"font-size:18px;width:18px;height:18px\">\n {{ collapsed ? 'chevron_right' : 'keyboard_double_arrow_left' }}\n </mat-icon>\n </button>\n <button\n *ngIf=\"!collapsed && allowCreate\"\n type=\"button\"\n class=\"cqa-p-1 cqa-rounded hover:cqa-bg-neutral-100 cqa-text-neutral-500\"\n (click)=\"requestCreate(null)\"\n [attr.aria-label]=\"labels.newFolder\"\n [title]=\"labels.newFolder\"\n >\n <mat-icon style=\"font-size:18px;width:18px;height:18px\">add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container *ngIf=\"!collapsed\">\n <!-- Search -->\n <div class=\"cqa-px-3 cqa-pb-2\">\n <div class=\"cqa-relative\">\n <cqa-search-bar\n size=\"sm\"\n [fullWidth]=\"true\"\n [value]=\"searchValue\"\n [placeholder]=\"labels.searchFoldersPlaceholder\"\n [showClear]=\"true\"\n (valueChange)=\"onSearchInput($event)\"\n (cleared)=\"onSearchClear()\"\n ></cqa-search-bar>\n <span\n *ngIf=\"folderSearchLoading\"\n class=\"cqa-absolute cqa-right-8 cqa-inset-y-0 cqa-flex cqa-items-center\"\n aria-live=\"polite\"\n aria-label=\"Searching folders\"\n >\n <mat-spinner diameter=\"14\" mode=\"indeterminate\"></mat-spinner>\n </span>\n </div>\n </div>\n\n <!-- Tree (folders only \u2014 Unorganized sits directly below; tree caps at 60vh and scrolls when overflowing).\n Scroll-based lazy-loading at the root level: nearing the bottom emits\n `rootLoadMoreRequested`, which the host responds to by fetching the\n next page of roots. -->\n <div role=\"tree\" class=\"cqa-min-h-0 cqa-max-h-[60vh] cqa-overflow-y-auto cqa-py-1 cqa-scrollbar-thin cqa-scrollbar-track-transparent cqa-scrollbar-thumb-[#E5E7EB] cqa-scrollbar-thumb-rounded-full cqa-scrollbar-thumb-hover:cqa-bg-[#D1D5DB]\"\n (scroll)=\"onTreeScroll($event)\">\n <!-- Initial-load spinner: only renders when we have no folders yet AND\n no search active (search has its own inline spinner). -->\n <div\n *ngIf=\"rootFoldersLoading && folders.length === 0 && !searchValue?.trim()\"\n class=\"cqa-flex cqa-items-center cqa-justify-center cqa-py-6\"\n aria-live=\"polite\"\n >\n <mat-spinner diameter=\"20\" mode=\"indeterminate\"></mat-spinner>\n </div>\n <div\n *ngIf=\"searchValue?.trim() && rows.length === 0 && !folderSearchLoading\"\n class=\"cqa-px-3 cqa-py-2 cqa-text-sm cqa-text-neutral-500 cqa-text-center\"\n >\n {{ labels.noFoldersFound }}\n </div>\n <ng-container *ngFor=\"let row of rows; let i = index; trackBy: trackByRow\">\n <!-- Synthetic loading row (lazy fetch in flight) -->\n <div\n *ngIf=\"row.kind === 'loading'\"\n [style.paddingLeft.px]=\"8 + row.depth * 16\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-py-1.5 cqa-text-sm cqa-text-neutral-500\"\n aria-live=\"polite\"\n >\n <mat-spinner diameter=\"14\" mode=\"indeterminate\"></mat-spinner>\n <span>Loading\u2026</span>\n </div>\n <!-- Synthetic load-more row (more children available on backend) -->\n <button\n *ngIf=\"row.kind === 'load-more'\"\n type=\"button\"\n [style.paddingLeft.px]=\"8 + row.depth * 16\"\n class=\"cqa-flex cqa-items-center cqa-w-full cqa-py-1.5 cqa-text-sm cqa-text-indigo-600 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onLoadMore(row.node); $event.stopPropagation()\"\n >\n <span>Load more ({{ (row.node.totalChildren ?? 0) - (row.node.children?.length ?? 0) }})</span>\n </button>\n <!-- Real folder row -->\n <div\n *ngIf=\"row.kind === 'folder'\"\n role=\"treeitem\"\n tabindex=\"0\"\n [attr.aria-expanded]=\"row.hasChildren ? isExpanded(row.node.id) : null\"\n [attr.aria-selected]=\"isSelected(row.node.id)\"\n [attr.data-folder-row-id]=\"row.node.id\"\n [cqaFolderDrop]=\"row.node.id\"\n [dropEnabled]=\"allowDrop\"\n (testsDropped)=\"rowDropped(row.node.id, $event)\"\n (folderDropped)=\"onFolderRowDropped(row.node.id, $event)\"\n [cqaFolderDrag]=\"row.node.id\"\n [dragEnabled]=\"allowDrop\"\n (dragstart)=\"onFolderDragStart(row.node)\"\n (dragend)=\"onFolderDragEnd()\"\n (dragover)=\"onFolderRowDragOver(row)\"\n (dragleave)=\"onFolderRowDragLeave(row)\"\n (click)=\"onSelect(row.node)\"\n (contextmenu)=\"openContextMenu(row.node, $event)\"\n (keydown)=\"onRowKeydown($event, row, i)\"\n class=\"cqa-group cqa-flex cqa-items-center cqa-gap-1 cqa-pr-3 cqa-py-1.5 cqa-cursor-pointer hover:cqa-bg-indigo-50 focus:cqa-outline-none focus:cqa-bg-indigo-50 cqa-relative\"\n [class.cqa-bg-indigo-50]=\"isSelected(row.node.id) || contextMenuFolderId === row.node.id\"\n [style.paddingLeft.px]=\"8 + row.depth * 16\"\n [attr.title]=\"row.node.totalCount != null ? (row.node.totalCount + ' total including subfolders') : null\"\n >\n <span\n *ngIf=\"isSelected(row.node.id)\"\n class=\"cqa-absolute cqa-left-0 cqa-top-0 cqa-bottom-0 cqa-w-[3px] cqa-bg-indigo-600\"\n ></span>\n <button\n type=\"button\"\n class=\"cqa-p-0\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n [style.visibility]=\"row.hasChildren ? 'visible' : 'hidden'\"\n (click)=\"onToggle(row.node, $event)\"\n [attr.aria-label]=\"isExpanded(row.node.id) ? 'Collapse' : 'Expand'\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">\n {{ isExpanded(row.node.id) ? 'expand_more' : 'chevron_right' }}\n </mat-icon>\n </button>\n <ng-container *ngTemplateOutlet=\"folderIcon; context: { color: row.node.color }\"></ng-container>\n\n <ng-container *ngIf=\"renamingId === row.node.id; else nameTpl\">\n <input\n type=\"text\"\n size=\"1\"\n [attr.data-folder-rename-input]=\"row.node.id\"\n [attr.maxlength]=\"renameMaxLength\"\n class=\"cqa-flex-1 cqa-min-w-0 cqa-w-full cqa-px-2 cqa-py-0.5 cqa-rounded cqa-border cqa-bg-white cqa-text-sm cqa-shadow-sm focus:cqa-outline-none focus:cqa-ring-1\"\n [ngClass]=\"(isRenameDraftValid && !renameLimitFlash)\n ? 'cqa-border-neutral-300 focus:cqa-border-indigo-400 focus:cqa-ring-indigo-200'\n : 'cqa-border-[#EF4444] focus:cqa-border-[#EF4444] focus:cqa-ring-[#FCA5A5]'\"\n [attr.aria-invalid]=\"!isRenameDraftValid || renameLimitFlash\"\n [(ngModel)]=\"renameDraft\"\n (keydown)=\"onRenameKey($event, row.node)\"\n (keypress)=\"$event.stopPropagation()\"\n (keyup)=\"$event.stopPropagation()\"\n (blur)=\"commitRename(row.node)\"\n (click)=\"$event.stopPropagation()\"\n />\n </ng-container>\n <ng-template #nameTpl>\n <span\n class=\"cqa-flex-1 cqa-text-sm cqa-truncate\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n (dblclick)=\"beginRename(row.node, $event)\"\n >{{ row.node.name }}</span>\n </ng-template>\n\n <!-- Count shown at rest; hidden on row hover when delete is allowed -->\n <span\n *ngIf=\"showCounts && row.node.count != null\"\n class=\"cqa-text-xs cqa-tabular-nums cqa-ml-1\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n [class.group-hover:cqa-hidden]=\"allowDelete\"\n >{{ row.node.count }}</span>\n\n <button\n *ngIf=\"hasAnyContextAction\"\n type=\"button\"\n class=\"cqa-p-0.5 cqa-rounded hover:cqa-bg-neutral-200\"\n [style.color]=\"isSelected(row.node.id) || contextMenuFolderId === row.node.id ? '#3F43EE' : '#4C4C51'\"\n [class.cqa-hidden]=\"contextMenuFolderId !== row.node.id\"\n [class.group-hover:cqa-inline-flex]=\"true\"\n [class.cqa-inline-flex]=\"contextMenuFolderId === row.node.id\"\n (click)=\"openContextMenu(row.node, $event)\"\n [attr.aria-label]=\"'Open actions for ' + row.node.name\"\n [attr.aria-haspopup]=\"'menu'\"\n [attr.aria-expanded]=\"contextMenuFolderId === row.node.id\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">more_horiz</mat-icon>\n </button>\n </div>\n </ng-container>\n <!-- Root-level pagination is now driven by scroll (see `(scroll)` on the\n tree container above). Per-folder children retain the explicit\n \"Load more\" button rendered inside the rows loop above, since\n subtrees aren't independently scrollable. -->\n <!-- Pagination spinner \u2014 visible only when fetching an additional page\n of roots (folders.length > 0 distinguishes from initial-load case). -->\n <div\n *ngIf=\"rootFoldersLoading && folders.length > 0 && !searchValue?.trim()\"\n class=\"cqa-flex cqa-items-center cqa-justify-center cqa-py-2\"\n aria-live=\"polite\"\n >\n <mat-spinner diameter=\"14\" mode=\"indeterminate\"></mat-spinner>\n </div>\n </div>\n\n <!-- Divider between folder tree and pinned Unorganized tab -->\n <div\n aria-hidden=\"true\"\n class=\"cqa-mx-3 cqa-shrink-0\"\n style=\"height: 1px; background-color: #E5E7EB; margin-top: 6px; margin-bottom: 6px;\"\n ></div>\n\n <!-- Unorganized \u2014 pinned below the scrollable folder tree so it stays visible regardless of folder count -->\n <div\n role=\"treeitem\"\n tabindex=\"0\"\n [attr.aria-selected]=\"isSelected(null)\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-px-3 cqa-py-1.5 cqa-cursor-pointer hover:cqa-bg-indigo-50 focus:cqa-outline-none focus:cqa-bg-indigo-50 cqa-relative cqa-shrink-0\"\n [class.cqa-bg-indigo-50]=\"isSelected(null)\"\n [cqaFolderDrop]=\"null\"\n [dropEnabled]=\"allowDrop\"\n (testsDropped)=\"rowDropped(null, $event)\"\n (folderDropped)=\"onFolderRowDropped(null, $event)\"\n (click)=\"onSelectUnorganized()\"\n >\n <span\n *ngIf=\"isSelected(null)\"\n class=\"cqa-absolute cqa-left-0 cqa-top-0 cqa-bottom-0 cqa-w-[3px] cqa-bg-indigo-600\"\n ></span>\n <mat-icon [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\" style=\"font-size:16px;width:16px;height:16px\">inbox</mat-icon>\n <span class=\"cqa-flex-1 cqa-text-sm\" [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\">{{ labels.unorganized }}</span>\n <span *ngIf=\"showCounts\" class=\"cqa-text-xs cqa-tabular-nums\" [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\">{{ unorganizedCount }}</span>\n </div>\n\n <!-- Folder context menu (right-click / ellipsis). A 0\u00D70 fixed-position\n anchor at the click coords drives a cdkConnectedOverlay; CDK's\n flexible-connected positioning auto-flips up/left when the menu\n would overflow the viewport (last-folder-near-bottom case). -->\n <div #ctxAnchor cdkOverlayOrigin\n class=\"cqa-fixed cqa-w-0 cqa-h-0 cqa-pointer-events-none\"\n [style.left.px]=\"contextMenuPosition.x\"\n [style.top.px]=\"contextMenuPosition.y\"></div>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"ctxAnchor\"\n [cdkConnectedOverlayOpen]=\"contextMenuFolderId !== null\"\n [cdkConnectedOverlayPositions]=\"contextMenuPositions\"\n [cdkConnectedOverlayHasBackdrop]=\"false\"\n [cdkConnectedOverlayFlexibleDimensions]=\"false\"\n [cdkConnectedOverlayPush]=\"true\"\n (overlayOutsideClick)=\"onOverlayOutsideClick()\"\n (detach)=\"closeContextMenu()\"\n >\n <!-- The lib's tailwind config sets important: '.cqa-ui-root', which\n emits descendant selectors like `.cqa-ui-root .cqa-bg-white`.\n CDK Overlay teleports this template into the global overlay\n container (outside the host's cqa-ui-root), so we wrap the menu in\n an ancestor cqa-ui-root div to re-establish the utility scope. -->\n <div class=\"cqa-ui-root\">\n <div\n role=\"menu\"\n class=\"cqa-min-w-[180px] cqa-bg-white cqa-border cqa-border-neutral-200 cqa-rounded-md cqa-shadow-lg cqa-py-1\"\n (click)=\"$event.stopPropagation()\"\n (contextmenu)=\"$event.preventDefault(); $event.stopPropagation()\"\n >\n <ng-container *ngIf=\"folderById(contextMenuFolderId) as menuNode\">\n <button\n *ngIf=\"allowCreate\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextCreateSubfolder(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">create_new_folder</mat-icon>\n <span>{{ labels.folderMenuCreateSubfolder }}</span>\n </button>\n <button\n *ngIf=\"allowRename\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextRename(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">edit</mat-icon>\n <span>{{ labels.folderMenuRename }}</span>\n </button>\n <button\n *ngIf=\"allowMove\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextMove(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">drive_file_move</mat-icon>\n <span>{{ labels.folderMenuMove }}</span>\n </button>\n <button\n *ngIf=\"allowDuplicate\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextDuplicate(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">content_copy</mat-icon>\n <span>{{ labels.folderMenuDuplicate }}</span>\n </button>\n <div *ngIf=\"allowDelete && (allowCreate || allowRename || allowMove || allowDuplicate)\" class=\"cqa-h-px cqa-bg-neutral-200 cqa-my-1\"></div>\n <button\n *ngIf=\"allowDelete\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-red-600 hover:cqa-bg-red-50 cqa-text-left\"\n (click)=\"onContextDelete(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">delete_outline</mat-icon>\n <span>{{ labels.folderMenuDelete }}</span>\n </button>\n </ng-container>\n </div>\n </div>\n </ng-template>\n\n </ng-container>\n</aside>\n", styles: [] }]
9613
+ args: [{ selector: 'cqa-folder-sidebar', host: { class: 'cqa-ui-root' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Reusable indeterminate loading spinner. Render via\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 14 }\"></ng-container>. -->\n<ng-template #loadingSpinner let-size=\"size\">\n <svg [attr.width]=\"size || 16\" [attr.height]=\"size || 16\" viewBox=\"0 0 50 50\" aria-label=\"loading\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"25\" cy=\"25\" r=\"20\" stroke=\"#E5E7EB\" stroke-width=\"6\" fill=\"none\"></circle>\n <path d=\"M45 25a20 20 0 0 0-20-20\" stroke=\"#4F46E5\" stroke-width=\"6\" fill=\"none\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 25 25\" to=\"360 25 25\" dur=\"0.8s\" repeatCount=\"indefinite\"></animateTransform>\n </path>\n </svg>\n</ng-template>\n\n<!-- Reusable folder icon. Render via <ng-container *ngTemplateOutlet=\"folderIcon; context: { color: node.color }\"></ng-container>. -->\n<ng-template #folderIcon let-color=\"color\">\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-4 cqa-h-4 cqa-flex-shrink-0\">\n <svg width=\"13\" height=\"12\" viewBox=\"0 0 13 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.1916 9.85824C12.1916 10.1677 12.0687 10.4644 11.8499 10.6832C11.6311 10.902 11.3343 11.0249 11.0249 11.0249H1.69157C1.38215 11.0249 1.0854 10.902 0.866611 10.6832C0.647819 10.4644 0.524902 10.1677 0.524902 9.85824V1.69157C0.524902 1.38215 0.647819 1.0854 0.866611 0.866611C1.0854 0.647819 1.38215 0.524902 1.69157 0.524902H4.60824L5.7749 2.2749H11.0249C11.3343 2.2749 11.6311 2.39782 11.8499 2.61661C12.0687 2.8354 12.1916 3.13215 12.1916 3.44157V9.85824Z\" [attr.stroke]=\"color || '#99999E'\" stroke-width=\"1.05\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n</ng-template>\n\n<aside\n class=\"cqa-flex cqa-flex-col cqa-bg-white cqa-h-full cqa-min-h-0\"\n [class.cqa-border]=\"!collapsed\"\n [class.cqa-border-neutral-200]=\"!collapsed\"\n [class.cqa-rounded-lg]=\"!collapsed\"\n [style.width.px]=\"collapsed ? 22 : 240\"\n style=\"overflow: hidden; transition: width 260ms cubic-bezier(0.4, 0, 0.2, 1);\"\n>\n <!-- Collapsed rail: the entire sidebar becomes a single click target so the\n slim strip reads as an obvious \"expand me\" handle. -->\n <button\n *ngIf=\"collapsed\"\n type=\"button\"\n style=\"width: 22px;\"\n class=\"cqa-group cqa-flex cqa-flex-col cqa-items-center cqa-flex-shrink-0 cqa-h-full cqa-py-2 cqa-gap-3 cqa-bg-neutral-50 cqa-border cqa-border-neutral-200 cqa-text-neutral-500 hover:cqa-bg-indigo-50 hover:cqa-border-indigo-200 hover:cqa-text-indigo-600 focus:cqa-outline-none focus:cqa-ring-2 focus:cqa-ring-indigo-200 cqa-cursor-pointer\"\n (click)=\"togglePanel()\"\n [attr.aria-label]=\"'Expand folders panel'\"\n [title]=\"'Expand folders panel'\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">chevron_right</mat-icon>\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center\" aria-hidden=\"true\">\n <svg width=\"13\" height=\"12\" viewBox=\"0 0 13 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M12.1916 9.85824C12.1916 10.1677 12.0687 10.4644 11.8499 10.6832C11.6311 10.902 11.3343 11.0249 11.0249 11.0249H1.69157C1.38215 11.0249 1.0854 10.902 0.866611 10.6832C0.647819 10.4644 0.524902 10.1677 0.524902 9.85824V1.69157C0.524902 1.38215 0.647819 1.0854 0.866611 0.866611C1.0854 0.647819 1.38215 0.524902 1.69157 0.524902H4.60824L5.7749 2.2749H11.0249C11.3343 2.2749 11.6311 2.39782 11.8499 2.61661C12.0687 2.8354 12.1916 3.13215 12.1916 3.44157V9.85824Z\" stroke=\"currentColor\" stroke-width=\"1.05\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </button>\n\n <div *ngIf=\"!collapsed\" class=\"cqa-flex cqa-flex-col cqa-h-full cqa-min-h-0 cqa-pb-2\" style=\"width: 240px;\">\n <!-- Header (expanded) -->\n <div\n class=\"cqa-flex cqa-items-center cqa-justify-between cqa-py-1 cqa-px-3\"\n style=\"border-bottom: 1px solid #E2E2E3; margin-bottom: 8px;\"\n >\n <span class=\"cqa-text-sm cqa-font-semibold cqa-text-neutral-900 cqa-py-0.5\">{{ labels.folders }}</span>\n <div class=\"cqa-flex cqa-items-center cqa-gap-1\">\n <button\n type=\"button\"\n class=\"cqa-p-1 cqa-rounded hover:cqa-bg-neutral-100 cqa-text-neutral-500\"\n (click)=\"togglePanel()\"\n [attr.aria-label]=\"'Collapse folders panel'\"\n [title]=\"'Collapse folders panel'\"\n >\n <mat-icon style=\"font-size:18px;width:18px;height:18px\">keyboard_double_arrow_left</mat-icon>\n </button>\n <button\n type=\"button\"\n class=\"cqa-p-1 cqa-rounded hover:cqa-bg-neutral-100\"\n [class.cqa-text-neutral-500]=\"!searchVisible\"\n [class.cqa-text-indigo-600]=\"searchVisible\"\n [class.cqa-bg-neutral-100]=\"searchVisible\"\n (click)=\"toggleSearchVisible()\"\n [attr.aria-label]=\"searchVisible ? 'Hide folder search' : 'Search folders'\"\n [attr.aria-pressed]=\"searchVisible\"\n [title]=\"searchVisible ? 'Hide folder search' : 'Search folders'\"\n >\n <mat-icon style=\"font-size:18px;width:18px;height:18px\">search</mat-icon>\n </button>\n <button\n *ngIf=\"allowCreate\"\n type=\"button\"\n class=\"cqa-p-1 cqa-rounded hover:cqa-bg-neutral-100 cqa-text-neutral-500\"\n (click)=\"requestCreate(null)\"\n [attr.aria-label]=\"labels.newFolder\"\n [title]=\"labels.newFolder\"\n >\n <mat-icon style=\"font-size:18px;width:18px;height:18px\">add</mat-icon>\n </button>\n </div>\n </div>\n\n <ng-container>\n <!-- Search (toggled via the search icon in the header) -->\n <div\n class=\"cqa-grid\"\n style=\"transition: grid-template-rows 300ms cubic-bezier(0.4, 0, 0.2, 1);\"\n [style.grid-template-rows]=\"searchVisible ? '1fr' : '0fr'\"\n >\n <div class=\"cqa-min-h-0\" style=\"overflow: hidden;\">\n <div class=\"cqa-px-3 cqa-pb-2\">\n <div class=\"cqa-relative\">\n <cqa-search-bar\n size=\"sm\"\n [fullWidth]=\"true\"\n [value]=\"searchValue\"\n [autoFocus]=\"searchVisible\"\n [placeholder]=\"labels.searchFoldersPlaceholder\"\n [showClear]=\"true\"\n (valueChange)=\"onSearchInput($event)\"\n (cleared)=\"onSearchClear()\"\n ></cqa-search-bar>\n <span\n *ngIf=\"folderSearchLoading\"\n class=\"cqa-absolute cqa-right-8 cqa-inset-y-0 cqa-flex cqa-items-center\"\n aria-live=\"polite\"\n aria-label=\"Searching folders\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 14 }\"></ng-container>\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Organized section header \u2014 visually distinct from folder rows so it reads\n as a view/section toggle rather than a folder item. -->\n <button\n type=\"button\"\n class=\"cqa-flex cqa-items-center cqa-justify-between cqa-w-full cqa-px-3 cqa-py-1 cqa-text-left cqa-text-[#6D6D74] hover:cqa-text-[#3F43EE] focus:cqa-outline-none focus:cqa-text-[#3F43EE] cqa-flex-shrink-0 cqa-uppercase\"\n (click)=\"toggleFoldersAccordion()\"\n [attr.aria-expanded]=\"foldersAccordionExpanded\"\n [attr.aria-label]=\"foldersAccordionExpanded ? 'Collapse organized folders' : 'Expand organized folders'\"\n >\n <span class=\"cqa-text-[11px] cqa-font-semibold cqa-tracking-wider\">{{ labels.organized }}</span>\n <mat-icon\n style=\"font-size:16px;width:16px;height:16px;transition: transform 200ms ease;\"\n [style.transform]=\"foldersAccordionExpanded ? 'rotate(0deg)' : 'rotate(-90deg)'\"\n >expand_more</mat-icon>\n </button>\n\n <!-- Tree (folders only \u2014 Unorganized sits directly below; tree caps at 60vh and scrolls when overflowing).\n Scroll-based lazy-loading at the root level: nearing the bottom emits\n `rootLoadMoreRequested`, which the host responds to by fetching the\n next page of roots. -->\n <div\n class=\"cqa-grid cqa-min-h-0\"\n style=\"transition: grid-template-rows 300ms cubic-bezier(0.4, 0, 0.2, 1);\"\n [style.grid-template-rows]=\"foldersAccordionExpanded ? '1fr' : '0fr'\"\n >\n <div class=\"cqa-min-h-0\" style=\"overflow: hidden;\">\n <div role=\"tree\" class=\"cqa-min-h-0 cqa-max-h-[40vh] cqa-overflow-y-auto cqa-py-1 cqa-scrollbar-thin cqa-scrollbar-track-transparent cqa-scrollbar-thumb-[#E5E7EB] cqa-scrollbar-thumb-rounded-full cqa-scrollbar-thumb-hover:cqa-bg-[#D1D5DB]\"\n (scroll)=\"onTreeScroll($event)\">\n <!-- Initial-load spinner: only renders when we have no folders yet AND\n no search active (search has its own inline spinner). -->\n <div\n *ngIf=\"rootFoldersLoading && folders.length === 0 && !searchValue?.trim()\"\n class=\"cqa-flex cqa-items-center cqa-justify-center cqa-py-6\"\n aria-live=\"polite\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 20 }\"></ng-container>\n </div>\n <div\n *ngIf=\"searchValue?.trim() && rows.length === 0 && !folderSearchLoading\"\n class=\"cqa-px-3 cqa-py-2 cqa-text-sm cqa-text-neutral-500 cqa-text-center\"\n >\n {{ labels.noFoldersFound }}\n </div>\n <ng-container *ngFor=\"let row of rows; let i = index; trackBy: trackByRow\">\n <!-- Synthetic loading row (lazy fetch in flight) -->\n <div\n *ngIf=\"row.kind === 'loading'\"\n [style.paddingLeft.px]=\"rowIndent(row.depth)\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-py-1.5 cqa-text-sm cqa-text-neutral-500\"\n aria-live=\"polite\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 14 }\"></ng-container>\n <span>Loading\u2026</span>\n </div>\n <!-- Synthetic load-more row (more children available on backend) -->\n <button\n *ngIf=\"row.kind === 'load-more'\"\n type=\"button\"\n [style.paddingLeft.px]=\"rowIndent(row.depth)\"\n class=\"cqa-flex cqa-items-center cqa-w-full cqa-py-1.5 cqa-text-sm cqa-text-indigo-600 hover:cqa-bg-[#D8D9FC] cqa-text-left\"\n (click)=\"onLoadMore(row.node); $event.stopPropagation()\"\n >\n <span>Load more ({{ (row.node.totalChildren ?? 0) - (row.node.children?.length ?? 0) }})</span>\n </button>\n <!-- Real folder row -->\n <div\n *ngIf=\"row.kind === 'folder'\"\n role=\"treeitem\"\n tabindex=\"0\"\n [attr.aria-expanded]=\"row.hasChildren ? isExpanded(row.node.id) : null\"\n [attr.aria-selected]=\"isSelected(row.node.id)\"\n [attr.data-folder-row-id]=\"row.node.id\"\n [cqaFolderDrop]=\"row.node.id\"\n [dropEnabled]=\"allowDrop\"\n (testsDropped)=\"rowDropped(row.node.id, $event)\"\n (folderDropped)=\"onFolderRowDropped(row.node.id, $event)\"\n [cqaFolderDrag]=\"row.node.id\"\n [dragEnabled]=\"allowDrop\"\n (dragstart)=\"onFolderDragStart(row.node)\"\n (dragend)=\"onFolderDragEnd()\"\n (dragover)=\"onFolderRowDragOver(row)\"\n (dragleave)=\"onFolderRowDragLeave(row)\"\n (click)=\"onSelect(row.node)\"\n (contextmenu)=\"openContextMenu(row.node, $event)\"\n (keydown)=\"onRowKeydown($event, row, i)\"\n class=\"cqa-group cqa-flex cqa-items-center cqa-gap-1 cqa-pr-3 cqa-py-1.5 cqa-cursor-pointer hover:cqa-bg-[#D8D9FC] focus:cqa-outline-none focus:cqa-bg-[#D8D9FC] cqa-relative\"\n [style.background-color]=\"(isSelected(row.node.id) || contextMenuFolderId === row.node.id) ? '#D8D9FC' : null\"\n [style.paddingLeft.px]=\"rowIndent(row.depth)\"\n [attr.title]=\"folderRowTooltip(row.node)\"\n >\n <span\n *ngIf=\"isSelected(row.node.id)\"\n class=\"cqa-absolute cqa-left-0 cqa-top-0 cqa-bottom-0 cqa-w-[3px] cqa-bg-indigo-600\"\n ></span>\n <button\n type=\"button\"\n class=\"cqa-p-0\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n [style.visibility]=\"row.hasChildren ? 'visible' : 'hidden'\"\n (click)=\"onToggle(row.node, $event)\"\n [attr.aria-label]=\"isExpanded(row.node.id) ? 'Collapse' : 'Expand'\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">\n {{ isExpanded(row.node.id) ? 'expand_more' : 'chevron_right' }}\n </mat-icon>\n </button>\n <ng-container *ngTemplateOutlet=\"folderIcon; context: { color: row.node.color }\"></ng-container>\n\n <ng-container *ngIf=\"renamingId === row.node.id; else nameTpl\">\n <input\n type=\"text\"\n size=\"1\"\n [attr.data-folder-rename-input]=\"row.node.id\"\n [attr.maxlength]=\"renameMaxLength\"\n class=\"cqa-flex-1 cqa-min-w-0 cqa-w-full cqa-px-2 cqa-py-0.5 cqa-rounded cqa-border cqa-bg-white cqa-text-sm cqa-shadow-sm focus:cqa-outline-none focus:cqa-ring-1\"\n [ngClass]=\"(isRenameDraftValid && !renameLimitFlash)\n ? 'cqa-border-neutral-300 focus:cqa-border-indigo-400 focus:cqa-ring-indigo-200'\n : 'cqa-border-[#EF4444] focus:cqa-border-[#EF4444] focus:cqa-ring-[#FCA5A5]'\"\n [attr.aria-invalid]=\"!isRenameDraftValid || renameLimitFlash\"\n [(ngModel)]=\"renameDraft\"\n (keydown)=\"onRenameKey($event, row.node)\"\n (keypress)=\"$event.stopPropagation()\"\n (keyup)=\"$event.stopPropagation()\"\n (blur)=\"commitRename(row.node)\"\n (click)=\"$event.stopPropagation()\"\n />\n </ng-container>\n <ng-template #nameTpl>\n <span\n class=\"cqa-flex-1 cqa-text-sm cqa-truncate\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n (dblclick)=\"beginRename(row.node, $event)\"\n >{{ row.node.name }}</span>\n </ng-template>\n\n <!-- Saving indicator: replaces the count while the host's rename/save\n API call is in flight, so the user gets immediate feedback. -->\n <span\n *ngIf=\"isFolderSaving(row.node.id)\"\n class=\"cqa-flex cqa-items-center cqa-ml-1 cqa-flex-shrink-0\"\n aria-live=\"polite\"\n [attr.aria-label]=\"'Saving ' + row.node.name\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 12 }\"></ng-container>\n </span>\n\n <!-- Count shown at rest; hidden on row hover when delete is allowed -->\n <span\n *ngIf=\"!isFolderSaving(row.node.id) && showCounts && row.node.count != null\"\n class=\"cqa-text-xs cqa-tabular-nums cqa-ml-1\"\n [style.color]=\"isSelected(row.node.id) ? '#3F43EE' : '#4C4C51'\"\n [class.group-hover:cqa-hidden]=\"allowDelete\"\n >{{ row.node.count }}</span>\n\n <button\n *ngIf=\"hasAnyContextAction\"\n type=\"button\"\n class=\"cqa-p-0.5 cqa-rounded hover:cqa-bg-neutral-200\"\n [style.color]=\"isSelected(row.node.id) || contextMenuFolderId === row.node.id ? '#3F43EE' : '#4C4C51'\"\n [class.cqa-hidden]=\"contextMenuFolderId !== row.node.id\"\n [class.group-hover:cqa-inline-flex]=\"true\"\n [class.cqa-inline-flex]=\"contextMenuFolderId === row.node.id\"\n (click)=\"openContextMenu(row.node, $event)\"\n [attr.aria-label]=\"'Open actions for ' + row.node.name\"\n [attr.aria-haspopup]=\"'menu'\"\n [attr.aria-expanded]=\"contextMenuFolderId === row.node.id\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">more_horiz</mat-icon>\n </button>\n </div>\n </ng-container>\n <!-- Root-level pagination is now driven by scroll (see `(scroll)` on the\n tree container above). Per-folder children retain the explicit\n \"Load more\" button rendered inside the rows loop above, since\n subtrees aren't independently scrollable. -->\n <!-- Pagination spinner \u2014 visible only when fetching an additional page\n of roots (folders.length > 0 distinguishes from initial-load case). -->\n <div\n *ngIf=\"rootFoldersLoading && folders.length > 0 && !searchValue?.trim()\"\n class=\"cqa-flex cqa-items-center cqa-justify-center cqa-py-2\"\n aria-live=\"polite\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 14 }\"></ng-container>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Divider between folder tree and pinned Unorganized tab -->\n <div\n aria-hidden=\"true\"\n class=\"cqa-mx-3 cqa-flex-shrink-0\"\n style=\"height: 1px; background-color: #E5E7EB; margin-top: 6px; margin-bottom: 6px;\"\n ></div>\n\n <!-- Unorganized \u2014 pinned below the scrollable folder tree so it stays visible regardless of folder count -->\n <div\n role=\"treeitem\"\n tabindex=\"0\"\n [attr.aria-selected]=\"isSelected(null)\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-px-3 cqa-py-1.5 cqa-cursor-pointer hover:cqa-bg-[#D8D9FC] focus:cqa-outline-none focus:cqa-bg-[#D8D9FC] cqa-relative cqa-flex-shrink-0\"\n [style.background-color]=\"isSelected(null) ? '#D8D9FC' : null\"\n [cqaFolderDrop]=\"null\"\n [dropEnabled]=\"allowDrop\"\n (testsDropped)=\"rowDropped(null, $event)\"\n (folderDropped)=\"onFolderRowDropped(null, $event)\"\n (click)=\"onSelectUnorganized()\"\n >\n <span\n *ngIf=\"isSelected(null)\"\n class=\"cqa-absolute cqa-left-0 cqa-top-0 cqa-bottom-0 cqa-w-[3px] cqa-bg-indigo-600\"\n ></span>\n <mat-icon [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\" style=\"font-size:16px;width:16px;height:16px\">inbox</mat-icon>\n <span class=\"cqa-flex-1 cqa-text-sm\" [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\">{{ labels.unorganized }}</span>\n <span *ngIf=\"showCounts\" class=\"cqa-text-xs cqa-tabular-nums\" [style.color]=\"isSelected(null) ? '#3F43EE' : '#4C4C51'\">{{ unorganizedCount }}</span>\n </div>\n\n <!-- Folder context menu (right-click / ellipsis). A 0\u00D70 fixed-position\n anchor at the click coords drives a cdkConnectedOverlay; CDK's\n flexible-connected positioning auto-flips up/left when the menu\n would overflow the viewport (last-folder-near-bottom case). -->\n <div #ctxAnchor cdkOverlayOrigin\n class=\"cqa-fixed cqa-w-0 cqa-h-0 cqa-pointer-events-none\"\n [style.left.px]=\"contextMenuPosition.x\"\n [style.top.px]=\"contextMenuPosition.y\"></div>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"ctxAnchor\"\n [cdkConnectedOverlayOpen]=\"contextMenuFolderId !== null\"\n [cdkConnectedOverlayPositions]=\"contextMenuPositions\"\n [cdkConnectedOverlayHasBackdrop]=\"false\"\n [cdkConnectedOverlayFlexibleDimensions]=\"false\"\n [cdkConnectedOverlayPush]=\"true\"\n (overlayOutsideClick)=\"onOverlayOutsideClick()\"\n (detach)=\"closeContextMenu()\"\n >\n <!-- The lib's tailwind config sets important: '.cqa-ui-root', which\n emits descendant selectors like `.cqa-ui-root .cqa-bg-white`.\n CDK Overlay teleports this template into the global overlay\n container (outside the host's cqa-ui-root), so we wrap the menu in\n an ancestor cqa-ui-root div to re-establish the utility scope. -->\n <div class=\"cqa-ui-root\">\n <div\n role=\"menu\"\n class=\"cqa-min-w-[180px] cqa-bg-white cqa-border cqa-border-neutral-200 cqa-rounded-md cqa-shadow-lg cqa-py-1\"\n (click)=\"$event.stopPropagation()\"\n (contextmenu)=\"$event.preventDefault(); $event.stopPropagation()\"\n >\n <ng-container *ngIf=\"folderById(contextMenuFolderId) as menuNode\">\n <button\n *ngIf=\"allowCreate\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextCreateSubfolder(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">create_new_folder</mat-icon>\n <span>{{ labels.folderMenuCreateSubfolder }}</span>\n </button>\n <button\n *ngIf=\"allowRename\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextRename(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">edit</mat-icon>\n <span>{{ labels.folderMenuRename }}</span>\n </button>\n <button\n *ngIf=\"allowMove\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextMove(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">drive_file_move</mat-icon>\n <span>{{ labels.folderMenuMove }}</span>\n </button>\n <button\n *ngIf=\"allowDuplicate\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-neutral-800 hover:cqa-bg-indigo-50 cqa-text-left\"\n (click)=\"onContextDuplicate(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">content_copy</mat-icon>\n <span>{{ labels.folderMenuDuplicate }}</span>\n </button>\n <div *ngIf=\"allowDelete && (allowCreate || allowRename || allowMove || allowDuplicate)\" class=\"cqa-h-px cqa-bg-neutral-200 cqa-my-1\"></div>\n <button\n *ngIf=\"allowDelete\"\n type=\"button\"\n role=\"menuitem\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-w-full cqa-px-3 cqa-py-1.5 cqa-text-sm cqa-text-red-600 hover:cqa-bg-red-50 cqa-text-left\"\n (click)=\"onContextDelete(menuNode)\"\n >\n <mat-icon style=\"font-size:16px;width:16px;height:16px\">delete_outline</mat-icon>\n <span>{{ labels.folderMenuDelete }}</span>\n </button>\n </ng-container>\n </div>\n </div>\n </ng-template>\n\n </ng-container>\n </div>\n</aside>\n", styles: [] }]
9520
9614
  }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { folders: [{
9521
9615
  type: Input
9522
9616
  }], selectedFolderId: [{
@@ -9541,6 +9635,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
9541
9635
  type: Input
9542
9636
  }], collapsed: [{
9543
9637
  type: Input
9638
+ }], foldersAccordionExpanded: [{
9639
+ type: Input
9544
9640
  }], labels: [{
9545
9641
  type: Input
9546
9642
  }], serverSideSearch: [{
@@ -9551,6 +9647,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
9551
9647
  type: Input
9552
9648
  }], rootFoldersLoading: [{
9553
9649
  type: Input
9650
+ }], savingFolderIds: [{
9651
+ type: Input
9554
9652
  }], folderSelected: [{
9555
9653
  type: Output
9556
9654
  }], folderExpansionToggled: [{
@@ -9581,6 +9679,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
9581
9679
  type: Output
9582
9680
  }], collapsedChange: [{
9583
9681
  type: Output
9682
+ }], foldersAccordionExpandedChange: [{
9683
+ type: Output
9584
9684
  }], searchValue: [{
9585
9685
  type: Input
9586
9686
  }], onDocumentClick: [{
@@ -9747,6 +9847,12 @@ class ModularTableTemplateComponent {
9747
9847
  this.modularLabels = { ...DEFAULT_MODULAR_LABELS };
9748
9848
  this.bulkActions = [];
9749
9849
  this.sidebarCollapsed = false;
9850
+ /** Controls whether the "Subfolders in {parent}" section is expanded.
9851
+ * Two-way bindable as `[(subfolderSectionExpanded)]`. Defaults to true. */
9852
+ this.subfolderSectionExpanded = true;
9853
+ /** Controls whether the root-view "Organized" folder grid is expanded.
9854
+ * Two-way bindable as `[(organizedSectionExpanded)]`. Defaults to true. */
9855
+ this.organizedSectionExpanded = true;
9750
9856
  /** Lazy-load: when true, the sidebar emits `searchChange` and skips its
9751
9857
  * local name filter so the host can serve search results from the backend. */
9752
9858
  this.serverSideSearch = false;
@@ -9765,6 +9871,9 @@ class ModularTableTemplateComponent {
9765
9871
  * the modular grid renders at the right edge of the Organized row, and is
9766
9872
  * forwarded to the sidebar for its own loading affordance. */
9767
9873
  this.rootFoldersLoading = false;
9874
+ /** Folder ids whose host-side save (rename / move / etc.) is in flight.
9875
+ * Forwarded to the sidebar for loading state. */
9876
+ this.savingFolderIds = [];
9768
9877
  /** Override for the currently-selected folder node. Used as a fallback when
9769
9878
  * `findFolder(folders, selectedFolderId)` misses — e.g., during sidebar
9770
9879
  * search when `folders` holds only the matched results, the selected
@@ -9829,6 +9938,8 @@ class ModularTableTemplateComponent {
9829
9938
  */
9830
9939
  this.bulkActionInvoked = new EventEmitter();
9831
9940
  this.sidebarCollapsedChange = new EventEmitter();
9941
+ this.subfolderSectionExpandedChange = new EventEmitter();
9942
+ this.organizedSectionExpandedChange = new EventEmitter();
9832
9943
  // ============================================================================
9833
9944
  // Row-reorder surface
9834
9945
  // ============================================================================
@@ -9939,6 +10050,14 @@ class ModularTableTemplateComponent {
9939
10050
  this.viewModeChange.emit(next);
9940
10051
  }
9941
10052
  }
10053
+ toggleSubfolderSection() {
10054
+ this.subfolderSectionExpanded = !this.subfolderSectionExpanded;
10055
+ this.subfolderSectionExpandedChange.emit(this.subfolderSectionExpanded);
10056
+ }
10057
+ toggleOrganizedSection() {
10058
+ this.organizedSectionExpanded = !this.organizedSectionExpanded;
10059
+ this.organizedSectionExpandedChange.emit(this.organizedSectionExpanded);
10060
+ }
9942
10061
  /** Partial override for the default reorder labels ("Reorder", "Done", etc.). */
9943
10062
  set reorderLabels(partial) {
9944
10063
  this._reorderLabels = { ...DEFAULT_REORDER_LABELS, ...(partial || {}) };
@@ -11083,10 +11202,10 @@ class ModularTableTemplateComponent {
11083
11202
  }
11084
11203
  ModularTableTemplateComponent.GRID_SCROLL_LOAD_THRESHOLD_PX = 96;
11085
11204
  ModularTableTemplateComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: ModularTableTemplateComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: DialogService }], target: i0.ɵɵFactoryTarget.Component });
11086
- ModularTableTemplateComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: ModularTableTemplateComponent, selector: "cqa-modular-table-template", inputs: { searchPlaceholder: "searchPlaceholder", searchValue: "searchValue", showClear: "showClear", showSearchBar: "showSearchBar", showExportButton: "showExportButton", isExporting: "isExporting", filterConfig: "filterConfig", filterModel: "filterModel", showFilterPanel: "showFilterPanel", showFilterButton: "showFilterButton", otherButtons: "otherButtons", otherDropDownButtons: "otherDropDownButtons", otherSelectDropDownButtons: "otherSelectDropDownButtons", otherButtonLabel: "otherButtonLabel", otherButtonVariant: "otherButtonVariant", showOtherButton: "showOtherButton", showActionButton: "showActionButton", showSettingsButton: "showSettingsButton", showAutoRefreshButton: "showAutoRefreshButton", showViewModeToggle: "showViewModeToggle", viewMode: "viewMode", viewModeLabels: "viewModeLabels", data: "data", isEmptyState: "isEmptyState", emptyStateConfig: "emptyStateConfig", actions: "actions", chips: "chips", filterApplied: "filterApplied", columns: "columns", rowSelectable: "rowSelectable", selectedAutoRefreshInterval: "selectedAutoRefreshInterval", pageIndex: "pageIndex", pageSize: "pageSize", pageSizeOptions: "pageSizeOptions", pageSizeMenuDirection: "pageSizeMenuDirection", serverSidePagination: "serverSidePagination", totalElements: "totalElements", enableLocalSort: "enableLocalSort", isTableLoading: "isTableLoading", isTableDataLoading: "isTableDataLoading", cellJsonPathGetter: "cellJsonPathGetter", onJsonPathCopiedHandler: "onJsonPathCopiedHandler", selectedItems: "selectedItems", showSelectAllInToolbar: "showSelectAllInToolbar", showDismissInToolbar: "showDismissInToolbar", allSelectedInToolbar: "allSelectedInToolbar", folders: "folders", rootFolders: "rootFolders", selectedFolderId: "selectedFolderId", expandedFolderIds: "expandedFolderIds", unorganizedCount: "unorganizedCount", folderIdAccessor: "folderIdAccessor", modularConfig: "modularConfig", modularLabels: "modularLabels", bulkActions: "bulkActions", sidebarCollapsed: "sidebarCollapsed", serverSideSearch: "serverSideSearch", rootTotal: "rootTotal", folderSearchLoading: "folderSearchLoading", folderSearchValue: "folderSearchValue", rootFoldersLoading: "rootFoldersLoading", selectedFolderNode: "selectedFolderNode", selectedFolderTrail: "selectedFolderTrail", useInternalDialogs: "useInternalDialogs", showReorderButton: "showReorderButton", reorderSaving: "reorderSaving", reorderLabels: "reorderLabels", columnVisibility: "columnVisibility" }, outputs: { onSearchChange: "onSearchChange", onExportClick: "onExportClick", onApplyFilterClick: "onApplyFilterClick", onResetFilterClick: "onResetFilterClick", onClearAll: "onClearAll", removeChip: "removeChip", viewModeChange: "viewModeChange", pageChange: "pageChange", sortChange: "sortChange", folderSelected: "folderSelected", folderExpansionToggled: "folderExpansionToggled", folderChildrenRequested: "folderChildrenRequested", folderLoadMoreRequested: "folderLoadMoreRequested", rootLoadMoreRequested: "rootLoadMoreRequested", folderSearchChange: "folderSearchChange", folderCreated: "folderCreated", folderCreateRequested: "folderCreateRequested", folderRenamed: "folderRenamed", folderDeleted: "folderDeleted", testsMoved: "testsMoved", bulkActionClick: "bulkActionClick", bulkSelectAllChange: "bulkSelectAllChange", bulkDismiss: "bulkDismiss", bulkActionInvoked: "bulkActionInvoked", sidebarCollapsedChange: "sidebarCollapsedChange", reorderStart: "reorderStart", reorderCancel: "reorderCancel", reorderSave: "reorderSave", moveRequested: "moveRequested", selectedItemsChange: "selectedItemsChange", folderDeleteRequested: "folderDeleteRequested", folderMoveRequested: "folderMoveRequested", folderMoved: "folderMoved", folderDuplicateRequested: "folderDuplicateRequested", onReload: "onReload", onAutoRefreshClick: "onAutoRefreshClick", columnVisibilityChange: "columnVisibilityChange", autoRefreshIntervalChange: "autoRefreshIntervalChange" }, host: { classAttribute: "cqa-ui-root" }, viewQueries: [{ propertyName: "dynamicFilterComponent", first: true, predicate: DynamicFilterComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Reusable folder-icon chip. Render via <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>. -->\n<ng-template #folderIconChip let-color=\"color\">\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-10 cqa-h-10 cqa-rounded-lg cqa-bg-[#F0F0F1] cqa-text-[#6D6D74] cqa-shrink-0 group-hover:cqa-bg-indigo-50 group-hover:cqa-text-[#3F43EE]\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" [attr.stroke]=\"color || 'currentColor'\" stroke-width=\"0.916667\"/>\n </svg>\n </span>\n</ng-template>\n\n<!-- Reusable \"+\" chip used by the \"New folder\" tile. -->\n<ng-template #addIconChip>\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-10 cqa-h-10 cqa-rounded-lg cqa-bg-[#F0F0F1] cqa-text-[#6D6D74] cqa-shrink-0 group-hover:cqa-bg-indigo-50 group-hover:cqa-text-[#3F43EE]\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M8 3V13M3 8H13\" stroke=\"currentColor\" stroke-width=\"1.275\" stroke-linecap=\"round\"/>\n </svg>\n </span>\n</ng-template>\n\n<div class=\"cqa-ui-root\">\n <div class=\"cqa-w-full cqa-flex cqa-flex-col cqa-relative\">\n <div [class]=\"!showSearchBar ? 'cqa-justify-end' : 'cqa-justify-between'\" class=\"cqa-w-full cqa-flex cqa-items-center cqa-gap-3 cqa-flex-wrap cqa-mb-3\">\n <cqa-search-bar\n *ngIf=\"showSearchBar\"\n [placeholder]=\"searchPlaceholder\"\n [value]=\"searchValue\"\n [showClear]=\"showClear\"\n (valueChange)=\"valueChange($event)\"\n (search)=\"search($event)\"\n (cleared)=\"cleared()\"\n ></cqa-search-bar>\n <div class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-flex-wrap\">\n <cqa-button\n *ngIf=\"showExportButton\"\n variant=\"grey-solid\"\n icon=\"open_in_new\"\n [text]=\"isExporting ? 'Exporting...' : 'Export'\"\n [disabled]=\"isExporting\"\n (clicked)=\"exportCodeClick()\"\n >\n <span>{{ isExporting ? 'Exporting...' : 'Export' }}</span>\n </cqa-button>\n \n <!-- Export Code Modal -->\n <cqa-export-code-modal\n *ngIf=\"showExportButton\"\n [isOpen]=\"isExportModalOpen\"\n [cases]=\"selectedCasesForExport\"\n [disabled]=\"false\"\n (closeModal)=\"closeExportModal()\"\n (export)=\"onExportModalExport($event)\">\n </cqa-export-code-modal>\n <ng-container *ngFor=\"let dropdownTemplate of otherDropDownButtons; trackBy: trackByDropdownTemplateRef\">\n <ng-container *ngTemplateOutlet=\"dropdownTemplate\"></ng-container>\n </ng-container>\n\n <ng-container *ngFor=\"let selectDropdownTemplate of otherSelectDropDownButtons; trackBy: trackBySelectDropdownTemplateRef\">\n <ng-container *ngTemplateOutlet=\"selectDropdownTemplate\"></ng-container>\n </ng-container>\n \n <cqa-button\n *ngIf=\"showFilterButton\"\n variant=\"grey-solid\"\n icon=\"add\"\n [disabled]=\"isReordering\"\n (clicked)=\"toggleFilter()\"\n >\n <span class=\"cqa-flex cqa-items-center cqa-gap-1\">\n Filter \n <div [class]=\"arrowClasses\">\n <svg\n class=\"cqa-w-2 cqa-h-1 cqa-absolute cqa-left-[4px] cqa-top-[6px]\"\n viewBox=\"0 0 8 4\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M0 0L4 4L8 0\"\n stroke=\"#0B0B0C\"\n stroke-width=\"1.33\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </div>\n </span>\n </cqa-button>\n <cqa-column-visibility\n *ngIf=\"showSettingsButton\"\n [columns]=\"visibilityColumns\"\n [columnVisibility]=\"columnVisibility\"\n [selectedAutoRefreshInterval]=\"selectedAutoRefreshInterval\"\n (columnVisibilityChange)=\"onColumnVisibilityChange($event)\"\n (autoRefreshChange)=\"onAutoRefreshChange($event)\"\n ></cqa-column-visibility>\n <cqa-button\n *ngIf=\"showAutoRefreshButton\"\n variant=\"grey-solid\"\n icon=\"refresh\"\n (clicked)=\"handleRefreshClick()\"\n [tooltip]=\"'Refresh'\"\n tooltipPosition=\"below\"\n [disabled]=\"isReordering\"\n ></cqa-button>\n <ng-container *ngFor=\"let buttonTemplate of otherButtons; trackBy: trackByTemplateRef\">\n <ng-container *ngTemplateOutlet=\"buttonTemplate\"></ng-container>\n </ng-container>\n <cqa-segment-control\n *ngIf=\"showViewModeToggle\"\n size=\"lg\"\n [segments]=\"viewModeSegments\"\n [value]=\"viewMode\"\n (valueChange)=\"onViewModeChange($event)\"\n ></cqa-segment-control>\n <cqa-button\n *ngIf=\"showReorderButton && !isReordering\"\n variant=\"outlined\"\n icon=\"drag_indicator\"\n [text]=\"reorderLabels.reorderButton\"\n [disabled]=\"!pagedRows || pagedRows.length === 0 || isTableLoading || isTableDataLoading\"\n (clicked)=\"startReorder()\"\n ></cqa-button>\n <ng-container *ngIf=\"showReorderButton && isReordering\">\n <cqa-button\n variant=\"outlined\"\n [text]=\"reorderLabels.cancelButton\"\n [disabled]=\"reorderSaving\"\n (clicked)=\"cancelReorder()\"\n ></cqa-button>\n <cqa-button\n variant=\"filled\"\n [icon]=\"reorderSaving ? 'hourglass_empty' : ''\"\n [text]=\"reorderSaving ? reorderLabels.savingButton : reorderLabels.doneButton\"\n [disabled]=\"reorderSaving\"\n (clicked)=\"saveReorder()\"\n ></cqa-button>\n </ng-container>\n </div>\n </div>\n\n <!-- Reorder mode banner -->\n <div *ngIf=\"isReordering\" class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-3 cqa-px-3 cqa-py-2 cqa-rounded-md cqa-bg-[#FFFBEB] cqa-border cqa-border-[#FDE68A]\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <circle cx=\"3\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"3\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"3\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n </svg>\n <span class=\"cqa-text-sm cqa-font-semibold cqa-text-[#92400E]\">{{ reorderLabels.bannerTitle }}</span>\n <span class=\"cqa-text-sm cqa-text-[#92400E]\">{{ reorderLabels.bannerDescription }}</span>\n </div>\n\n <cqa-selected-filters\n [filterApplied]=\"filterApplied\"\n [chips]=\"chips\"\n (removeChip)=\"onRemoveChip($event)\"\n (clearAll)=\"onClearAllChips()\"\n (onClearAll)=\"onClearAll.emit()\"\n >\n </cqa-selected-filters>\n\n <cqa-dynamic-filter\n *ngIf=\"showFilterPanel\"\n [config]=\"filterConfig\"\n [model]=\"filterModel\"\n [showFilterPanel]=\"showFilterPanel\"\n (filtersChanged)=\"onFiltersChanged($event)\"\n (filtersApplied)=\"onFiltersApplied($event)\"\n (onApplyFilterClick)=\"onApplyFilterClick.emit($event)\"\n (onResetFilterClick)=\"handleResetFilterClick()\"\n >\n </cqa-dynamic-filter>\n\n <div class=\"cqa-flex cqa-items-stretch cqa-gap-3 cqa-min-h-0 \" style=\"border-bottom: 1px solid rgb(226, 226, 227);\">\n <!-- Sidebar (only shown in modular view) -->\n <cqa-folder-sidebar\n *ngIf=\"isModularView && modularConfig.showSidebar\"\n [folders]=\"folders\"\n [selectedFolderId]=\"selectedFolderId\"\n [expandedFolderIds]=\"expandedFolderIds\"\n [unorganizedCount]=\"unorganizedCount\"\n [allowCreate]=\"modularConfig.allowCreateFolder\"\n [allowRename]=\"modularConfig.allowRenameFolder\"\n [allowDelete]=\"modularConfig.allowDeleteFolder\"\n [allowDrop]=\"modularConfig.allowTestDragDrop\"\n [showCounts]=\"modularConfig.showCounts\"\n [collapsed]=\"sidebarCollapsed\"\n [labels]=\"modularLabels\"\n [serverSideSearch]=\"serverSideSearch\"\n [rootTotal]=\"rootTotal\"\n [folderSearchLoading]=\"folderSearchLoading\"\n [rootFoldersLoading]=\"rootFoldersLoading\"\n [searchValue]=\"folderSearchValue\"\n (folderSelected)=\"onFolderSelected($event)\"\n (folderExpansionToggled)=\"onFolderExpansionToggled($event)\"\n (folderChildrenRequested)=\"folderChildrenRequested.emit($event)\"\n (folderLoadMoreRequested)=\"folderLoadMoreRequested.emit($event)\"\n (rootLoadMoreRequested)=\"rootLoadMoreRequested.emit()\"\n (searchChange)=\"folderSearchChange.emit($event)\"\n (folderCreated)=\"onFolderCreated($event)\"\n (folderCreateRequested)=\"onFolderCreateRequested($event)\"\n (folderRenamed)=\"onFolderRenamed($event)\"\n (folderDeleted)=\"onFolderDeleted($event)\"\n (folderMoveRequested)=\"onFolderMoveRequested($event)\"\n (folderDuplicateRequested)=\"onFolderDuplicateRequested($event)\"\n (testsDropped)=\"onTestsDropped($event)\"\n (folderDropped)=\"onFolderDropped($event)\"\n (collapsedChange)=\"onSidebarCollapsedChange($event)\"\n ></cqa-folder-sidebar>\n\n <!-- Right pane -->\n <div\n class=\"cqa-flex-1 cqa-flex cqa-flex-col cqa-min-w-0\"\n [style.border-top]=\"isModularView ? '1px solid #E2E2E3' : null\"\n [style.border-left]=\"isModularView ? '1px solid #E2E2E3' : null\"\n [style.border-right]=\"isModularView ? '1px solid #E2E2E3' : null\"\n >\n <!-- Breadcrumb (modular view, folder drilled-in) -->\n <nav\n *ngIf=\"isModularView && modularConfig.showBreadcrumb && !isRootView\"\n aria-label=\"Folder path\"\n class=\"cqa-flex cqa-items-center cqa-gap-1 cqa-text-sm cqa-px-3 cqa-py-1 cqa-mb-3 cqa-rounded-none\"\n style=\"color: #6D6D74; border-bottom: 1px solid #E2E2E3;\"\n >\n <button type=\"button\" class=\"hover:cqa-text-[#3F43EE] cqa-inline-flex cqa-items-center cqa-gap-1\" (click)=\"onFolderSelected(null)\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" stroke=\"currentColor\" stroke-width=\"0.916667\"/>\n </svg>\n <span>{{ modularLabels.allFolders }}</span>\n </button>\n <ng-container *ngFor=\"let crumb of breadcrumbTrail; let last = last\">\n <mat-icon class=\"cqa-align-middle\" style=\"font-size:16px;width:16px;height:16px;color:#6D6D74;\">chevron_right</mat-icon>\n <button\n type=\"button\"\n [ngClass]=\"last\n ? 'cqa-text-[#161617] cqa-font-semibold'\n : 'cqa-text-[#6D6D74] hover:cqa-text-[#3F43EE]'\"\n [disabled]=\"last\"\n (click)=\"!last && onFolderSelected(crumb.id)\"\n >{{ crumb.name }}</button>\n </ng-container>\n </nav>\n\n <!-- Folder grid: modular + root view -->\n <section\n *ngIf=\"isModularView && isRootView && modularConfig.showFolderGrid && rootFolderTiles.length\"\n class=\"cqa-mb-4 cqa-px-5 cqa-pt-4 cqa-pb-[14px]\"\n style=\"border-bottom: 1px solid #F0F0F1;\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-2\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" stroke=\"#6D6D74\" stroke-width=\"0.916667\"/>\n </svg>\n <h3 class=\"cqa-text-sm cqa-font-semibold cqa-text-[#6D6D74]\">{{ modularLabels.organized }}</h3>\n <span class=\"cqa-text-xs cqa-text-[#6D6D74]\">\n {{ rootFolderTiles.length === 1 ? modularLabels.foldersCountSingular : modularLabels.foldersCountPlural.replace('{n}', '' + rootFolderTiles.length) }}\n </span>\n </div>\n <div class=\"cqa-flex cqa-flex-nowrap cqa-gap-3 cqa-overflow-x-auto cqa-scrollbar-thin cqa-pb-2\"\n (scroll)=\"onRootGridScroll($event)\">\n <button\n *ngFor=\"let f of rootFolderTiles\"\n type=\"button\"\n [cqaFolderDrop]=\"f.id\"\n [dropEnabled]=\"modularConfig.allowTestDragDrop\"\n (testsDropped)=\"onTestsDropped($event)\"\n (click)=\"onFolderSelected(f.id)\"\n class=\"cqa-group cqa-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-justify-between cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-solid cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-white hover:cqa-border-[#3F43EE] hover:cqa-shadow-sm cqa-transition-colors\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-3 cqa-min-w-0\">\n <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ f.name }}</span>\n <span *ngIf=\"modularConfig.showCounts\" class=\"cqa-text-sm cqa-text-neutral-500 cqa-truncate\">\n {{ (f.count ?? 0) === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + (f.count ?? 0)) }}<ng-container *ngIf=\"f.children?.length\"> \u00B7 {{ f.children!.length === 1 ? modularLabels.oneSubfolder : modularLabels.subfoldersCount.replace('{n}', '' + f.children!.length) }}</ng-container>\n </span>\n </div>\n </div>\n <mat-icon class=\"cqa-text-neutral-400 cqa-shrink-0\" style=\"font-size:20px;width:20px;height:20px\">chevron_right</mat-icon>\n </button>\n\n <!-- \"+ New folder\" tile -->\n <button\n *ngIf=\"modularConfig.allowCreateFolder\"\n type=\"button\"\n (click)=\"onFolderCreateRequested({ parentId: null })\"\n class=\"cqa-group cqa-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-dashed cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-[#FAFAFA] hover:cqa-border-[#3F43EE] hover:cqa-bg-white cqa-transition-colors\"\n >\n <ng-container *ngTemplateOutlet=\"addIconChip\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ modularLabels.newFolder }}</span>\n <span class=\"cqa-text-sm cqa-text-neutral-400\">{{ modularLabels.testsCountPlural.replace('{n}', '0') }}</span>\n </div>\n </button>\n\n <!-- Pagination spinner anchored at the right edge of the Organized\n row. Only renders during an in-flight root fetch (initial OR\n scroll-driven \"load more\"), and is filtered to suppress when\n the section is empty so we don't show a spinner inside an\n otherwise-blank row. -->\n <div\n *ngIf=\"rootFoldersLoading && rootFolderTiles.length > 0\"\n class=\"cqa-shrink-0 cqa-flex cqa-items-center cqa-justify-center cqa-w-[80px]\"\n aria-live=\"polite\"\n [attr.aria-label]=\"'Loading more folders'\"\n >\n <mat-spinner diameter=\"20\" mode=\"indeterminate\"></mat-spinner>\n </div>\n </div>\n </section>\n\n <!-- Subfolder section: modular + folder view. Shown whenever drilled in\n (even if no subfolders, so the \"+ New folder\" tile remains\n reachable, and so we can render a loading state during the\n drill-in fetch before children arrive). -->\n <section\n *ngIf=\"isModularView && !isRootView && modularConfig.showSubfolderSection && (subfolderTiles.length || currentFolderNode?.childrenLoading)\"\n class=\"cqa-mb-4 cqa-px-3 cqa-py-1\"\n >\n <div *ngIf=\"subfolderTiles.length || modularConfig.allowCreateFolder\" class=\"cqa-text-sm cqa-text-neutral-600 cqa-mb-2\">\n {{ modularLabels.subfoldersIn }} {{ breadcrumbTrail.length ? breadcrumbTrail[breadcrumbTrail.length - 1].name : '' }}\n </div>\n <div class=\"cqa-flex cqa-flex-nowrap cqa-gap-3 cqa-overflow-x-auto cqa-scrollbar-thin cqa-pb-2\"\n (scroll)=\"onSubfolderGridScroll($event)\">\n <button\n *ngFor=\"let f of subfolderTiles\"\n type=\"button\"\n [cqaFolderDrop]=\"f.id\"\n [dropEnabled]=\"modularConfig.allowTestDragDrop\"\n (testsDropped)=\"onTestsDropped($event)\"\n (click)=\"onFolderSelected(f.id)\"\n class=\"cqa-group cqa-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-justify-between cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-solid cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-white hover:cqa-border-[#3F43EE] hover:cqa-shadow-sm cqa-transition-colors\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-3 cqa-min-w-0\">\n <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ f.name }}</span>\n <span *ngIf=\"modularConfig.showCounts\" class=\"cqa-text-sm cqa-text-neutral-500 cqa-truncate\">\n {{ (f.count ?? 0) === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + (f.count ?? 0)) }}\n </span>\n </div>\n </div>\n <mat-icon class=\"cqa-text-neutral-400 cqa-shrink-0\" style=\"font-size:20px;width:20px;height:20px\">chevron_right</mat-icon>\n </button>\n\n <!-- \"+ New folder\" tile -->\n <button\n *ngIf=\"modularConfig.allowCreateFolder\"\n type=\"button\"\n (click)=\"onFolderCreateRequested({ parentId: selectedFolderId })\"\n class=\"cqa-group cqa-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-dashed cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-[#FAFAFA] hover:cqa-border-[#3F43EE] hover:cqa-bg-white cqa-transition-colors\"\n >\n <ng-container *ngTemplateOutlet=\"addIconChip\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ modularLabels.newFolder }}</span>\n <span class=\"cqa-text-sm cqa-text-neutral-400\">{{ modularLabels.testsCountPlural.replace('{n}', '0') }}</span>\n </div>\n </button>\n\n <!-- Subfolder fetch indicator. Two cases:\n - Drill-in fetch: no subfolderTiles yet, render a centered\n row spinner so the section isn't empty during the fetch.\n - Pagination fetch: tiles exist, anchor a small spinner at\n the right edge so the user knows the next page is loading. -->\n <div\n *ngIf=\"currentFolderNode?.childrenLoading\"\n class=\"cqa-shrink-0 cqa-flex cqa-items-center cqa-justify-center cqa-w-[80px]\"\n aria-live=\"polite\"\n [attr.aria-label]=\"'Loading subfolders'\"\n >\n <mat-spinner diameter=\"20\" mode=\"indeterminate\"></mat-spinner>\n </div>\n </div>\n </section>\n\n <!-- Folder panel header above the table: \"Hotel Search (3)\" + Clear filter -->\n <div\n *ngIf=\"isModularView && !isRootView && modularConfig.showUnorganizedSection\"\n class=\"cqa-flex cqa-mb-1 cqa-items-center cqa-justify-between cqa-px-4 cqa-py-3 cqa-rounded-t-lg cqa-bg-indigo-50 cqa-border cqa-border-indigo-100\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-2\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9585 10.4585C12.9585 10.79 12.8268 11.108 12.5924 11.3424C12.358 11.5768 12.04 11.7085 11.7085 11.7085H1.7085C1.37698 11.7085 1.05903 11.5768 0.824613 11.3424C0.590192 11.108 0.458496 10.79 0.458496 10.4585V1.7085C0.458496 1.37698 0.590192 1.05903 0.824613 0.824613C1.05903 0.590192 1.37698 0.458496 1.7085 0.458496H4.8335L6.0835 2.3335H11.7085C12.04 2.3335 12.358 2.46519 12.5924 2.69961C12.8268 2.93403 12.9585 3.25198 12.9585 3.5835V10.4585Z\" [attr.stroke]=\"currentFolderNode?.color || '#3F43EE'\" stroke-width=\"0.916667\"/>\n </svg>\n <span class=\"cqa-text-sm cqa-font-semibold cqa-text-indigo-700\">\n {{ currentFolderNode?.name }} ({{ currentFolderDirectCount }})\n </span>\n </div>\n <button\n type=\"button\"\n class=\"cqa-px-3 cqa-py-1 cqa-text-xs cqa-font-medium cqa-text-indigo-700 cqa-bg-white cqa-border cqa-border-indigo-200 cqa-rounded hover:cqa-bg-indigo-50\"\n (click)=\"onFolderSelected(null)\"\n >{{ modularLabels.clearFilter }}</button>\n </div>\n\n <!-- Unorganized section header at root view -->\n <div\n *ngIf=\"isModularView && isRootView && modularConfig.showUnorganizedSection\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-2 cqa-mt-2\" style=\"margin-left: 20px;\"\n >\n <mat-icon class=\"cqa-text-[#6D6D74]\" style=\"font-size:16px;width:16px;height:16px\">inbox</mat-icon>\n <h3 class=\"cqa-text-sm cqa-font-semibold cqa-text-[#6D6D74]\">{{ modularLabels.unorganized }}</h3>\n <span class=\"cqa-text-xs cqa-text-[#6D6D74]\">\n {{ unorganizedCount === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + unorganizedCount) }}\n </span>\n </div>\n\n <!-- Table -->\n <div class=\"cqa-rounded-[7px] cqa-overflow-hidden cqa-border-t cqa-border-l cqa-border-r cqa-border-grey-200 cqa-relative cqa-overflow-x-auto\"\n [attr.style]=\"pagedRows && pagedRows.length > 0 ? null : 'border-bottom: none !important'\">\n <ng-container *ngIf=\"(isTableLoading || isTableDataLoading) || (!effectiveIsEmptyState && pagedRows && pagedRows.length > 0); else storyEmptyTpl\">\n <app-dynamic-table\n [style.border-bottom]=\"pagedRows && pagedRows.length > 0 ? '1px solid rgb(226, 226, 227)' : null\"\n [columns]=\"computedColumns\"\n [data]=\"pagedRows\"\n [rowSelectable]=\"rowSelectable\"\n [enableLocalSort]=\"enableLocalSort && !isReordering\"\n [isTableLoading]=\"isTableLoading\"\n [isTableDataLoading]=\"isTableDataLoading\"\n [cellJsonPathGetter]=\"cellJsonPathGetter\"\n [onJsonPathCopiedHandler]=\"onJsonPathCopiedHandler\"\n [enableRowReorder]=\"isReordering\"\n (rowReorder)=\"onRowReorder($event)\"\n (sortChange)=\"sortChange.emit($event)\">\n <ng-template #emptyTableTpl>\n <div class=\"cqa-flex cqa-flex-col cqa-items-center cqa-justify-center cqa-py-8\">\n <img src=\"/assets/illustrations/empty-state.svg\" alt=\"No data\" class=\"cqa-w-32 cqa-h-32 cqa-mb-4\" />\n <h3 class=\"cqa-text-lg cqa-font-semibold cqa-mb-2\">No test cases</h3>\n <p class=\"cqa-text-sm cqa-text-neutral-500 cqa-mb-4\">Try adjusting filters or create a new test case.</p>\n <cqa-button variant=\"filled\" (clicked)=\"toggleFilter()\">Show Filters</cqa-button>\n </div>\n </ng-template>\n </app-dynamic-table>\n </ng-container>\n\n <ng-template #storyEmptyTpl>\n <div class=\"cqa-p-6 cqa-flex cqa-flex-col cqa-items-center cqa-justify-center\">\n <cqa-empty-state\n *ngIf=\"effectiveIsEmptyState\"\n [title]=\"effectiveEmptyStateConfig.title\"\n [description]=\"effectiveEmptyStateConfig.description\"\n [imageUrl]=\"effectiveEmptyStateConfig.imageUrl\"\n [actions]=\"effectiveEmptyStateConfig.actions\"\n (actionClick)=\"onEmptyAction($event)\"\n >\n </cqa-empty-state>\n </div>\n </ng-template>\n </div>\n\n <cqa-pagination\n [totalElements]=\"serverSidePagination ? totalElements : filteredRows.length\"\n [pageIndex]=\"pageIndex\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [pageSizeMenuDirection]=\"pageSizeMenuDirection\"\n [pageItemCount]=\"pagedRows.length\"\n (paginate)=\"onPaginate($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\"\n >\n </cqa-pagination>\n </div>\n </div>\n\n <!-- Reused bulk action toolbar -->\n <div *ngIf=\"anyRowSelected && modularConfig.allowBulkSelection && !isReordering\" class=\"cqa-absolute cqa-bottom-[18.75px] cqa-left-[50%] cqa-translate-x-[-50%] cqa-w-full lg:cqa-max-w-[68%] cqa-sm:max-w-[75%] cqa-max-w-[90%] cqa-z-[1]\">\n <cqa-table-action-toolbar\n [selectedItems]=\"selectedItems && selectedItems.length > 0 ? selectedItems : currentSelectedItems\"\n [actions]=\"effectiveBulkActions\"\n [showSelectAll]=\"showSelectAllInToolbar\"\n [allSelected]=\"allSelectedInToolbar\"\n [showDismiss]=\"showDismissInToolbar\"\n (actionClick)=\"onBulkAction($event)\"\n (selectAllChange)=\"onBulkSelectAll($event)\"\n (dismiss)=\"onBulkDismiss()\"\n ></cqa-table-action-toolbar>\n </div>\n\n </div>\n</div>\n\n", components: [{ type: SearchBarComponent, selector: "cqa-search-bar", inputs: ["placeholder", "value", "disabled", "showClear", "ariaLabel", "autoFocus", "size", "fullWidth"], outputs: ["valueChange", "search", "cleared"] }, { type: ButtonComponent, selector: "cqa-button", inputs: ["variant", "btnSize", "disabled", "loading", "icon", "iconPosition", "fullWidth", "iconColor", "type", "text", "customClass", "inlineStyles", "tooltip", "tooltipPosition"], outputs: ["clicked"] }, { type: ExportCodeModalComponent, selector: "cqa-export-code-modal", inputs: ["isOpen", "cases", "disabled"], outputs: ["closeModal", "export"] }, { type: ColumnVisibilityComponent, selector: "cqa-column-visibility", inputs: ["isStepGroup", "columns", "columnVisibility", "selectedAutoRefreshInterval"], outputs: ["columnVisibilityChange", "autoRefreshChange"] }, { type: SegmentControlComponent, selector: "cqa-segment-control", inputs: ["segments", "value", "disabled", "containerBgColor", "fullWidth", "size"], outputs: ["valueChange"] }, { type: SelectedFiltersComponent, selector: "cqa-selected-filters", inputs: ["filterApplied", "chips", "defaultChips", "defaultChipClass"], outputs: ["removeChip", "clearAll", "onClearAll"] }, { type: DynamicFilterComponent, selector: "cqa-dynamic-filter", inputs: ["config", "model", "showFilterPanel", "buttonLayout"], outputs: ["filtersApplied", "filtersChanged", "resetAction", "onApplyFilterClick", "onResetFilterClick"] }, { type: FolderSidebarComponent, selector: "cqa-folder-sidebar", inputs: ["folders", "selectedFolderId", "expandedFolderIds", "unorganizedCount", "allowCreate", "allowRename", "allowDelete", "allowMove", "allowDuplicate", "allowDrop", "showCounts", "collapsed", "labels", "serverSideSearch", "rootTotal", "folderSearchLoading", "rootFoldersLoading", "searchValue"], outputs: ["folderSelected", "folderExpansionToggled", "folderChildrenRequested", "folderLoadMoreRequested", "searchChange", "rootLoadMoreRequested", "folderCreated", "folderCreateRequested", "folderRenamed", "folderDeleted", "folderMoveRequested", "folderDuplicateRequested", "testsDropped", "folderDropped", "collapsedChange"] }, { type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { type: i4.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "diameter", "strokeWidth", "mode", "value"], exportAs: ["matProgressSpinner"] }, { type: DynamicTableComponent, selector: "app-dynamic-table", inputs: ["data", "columns", "emptyState", "gridTemplateColumns", "screenWidth", "enableSelectAll", "rowSelectable", "enableLocalSort", "isTableLoading", "isTableDataLoading", "cellJsonPathGetter", "onJsonPathCopiedHandler", "enableRowReorder", "reorderHandleTooltip"], outputs: ["sortChange", "rowReorder"] }, { type: EmptyStateComponent, selector: "cqa-empty-state", inputs: ["preset", "imageUrl", "title", "description", "actions"], outputs: ["actionClick"] }, { type: PaginationComponent, selector: "cqa-pagination", inputs: ["totalElements", "totalPages", "pageIndex", "pageSize", "pageItemCount", "pageSizeOptions", "pageSizeMenuDirection"], outputs: ["pageIndexChange", "pageSizeChange", "paginate"] }, { type: TableActionToolbarComponent, selector: "cqa-table-action-toolbar", inputs: ["selectedItems", "actions", "showSelectAll", "allSelected", "showDismiss"], outputs: ["actionClick", "selectAllChange", "dismiss"] }], directives: [{ type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }, { type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { type: FolderDropDirective, selector: "[cqaFolderDrop]", inputs: ["cqaFolderDrop", "dropEnabled"], outputs: ["testsDropped", "folderDropped"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11205
+ ModularTableTemplateComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: ModularTableTemplateComponent, selector: "cqa-modular-table-template", inputs: { searchPlaceholder: "searchPlaceholder", searchValue: "searchValue", showClear: "showClear", showSearchBar: "showSearchBar", showExportButton: "showExportButton", isExporting: "isExporting", filterConfig: "filterConfig", filterModel: "filterModel", showFilterPanel: "showFilterPanel", showFilterButton: "showFilterButton", otherButtons: "otherButtons", otherDropDownButtons: "otherDropDownButtons", otherSelectDropDownButtons: "otherSelectDropDownButtons", otherButtonLabel: "otherButtonLabel", otherButtonVariant: "otherButtonVariant", showOtherButton: "showOtherButton", showActionButton: "showActionButton", showSettingsButton: "showSettingsButton", showAutoRefreshButton: "showAutoRefreshButton", showViewModeToggle: "showViewModeToggle", viewMode: "viewMode", viewModeLabels: "viewModeLabels", data: "data", isEmptyState: "isEmptyState", emptyStateConfig: "emptyStateConfig", actions: "actions", chips: "chips", filterApplied: "filterApplied", columns: "columns", rowSelectable: "rowSelectable", selectedAutoRefreshInterval: "selectedAutoRefreshInterval", pageIndex: "pageIndex", pageSize: "pageSize", pageSizeOptions: "pageSizeOptions", pageSizeMenuDirection: "pageSizeMenuDirection", serverSidePagination: "serverSidePagination", totalElements: "totalElements", enableLocalSort: "enableLocalSort", isTableLoading: "isTableLoading", isTableDataLoading: "isTableDataLoading", cellJsonPathGetter: "cellJsonPathGetter", onJsonPathCopiedHandler: "onJsonPathCopiedHandler", selectedItems: "selectedItems", showSelectAllInToolbar: "showSelectAllInToolbar", showDismissInToolbar: "showDismissInToolbar", allSelectedInToolbar: "allSelectedInToolbar", folders: "folders", rootFolders: "rootFolders", selectedFolderId: "selectedFolderId", expandedFolderIds: "expandedFolderIds", unorganizedCount: "unorganizedCount", folderIdAccessor: "folderIdAccessor", modularConfig: "modularConfig", modularLabels: "modularLabels", bulkActions: "bulkActions", sidebarCollapsed: "sidebarCollapsed", subfolderSectionExpanded: "subfolderSectionExpanded", organizedSectionExpanded: "organizedSectionExpanded", serverSideSearch: "serverSideSearch", rootTotal: "rootTotal", folderSearchLoading: "folderSearchLoading", folderSearchValue: "folderSearchValue", rootFoldersLoading: "rootFoldersLoading", savingFolderIds: "savingFolderIds", selectedFolderNode: "selectedFolderNode", selectedFolderTrail: "selectedFolderTrail", useInternalDialogs: "useInternalDialogs", showReorderButton: "showReorderButton", reorderSaving: "reorderSaving", reorderLabels: "reorderLabels", columnVisibility: "columnVisibility" }, outputs: { onSearchChange: "onSearchChange", onExportClick: "onExportClick", onApplyFilterClick: "onApplyFilterClick", onResetFilterClick: "onResetFilterClick", onClearAll: "onClearAll", removeChip: "removeChip", viewModeChange: "viewModeChange", pageChange: "pageChange", sortChange: "sortChange", folderSelected: "folderSelected", folderExpansionToggled: "folderExpansionToggled", folderChildrenRequested: "folderChildrenRequested", folderLoadMoreRequested: "folderLoadMoreRequested", rootLoadMoreRequested: "rootLoadMoreRequested", folderSearchChange: "folderSearchChange", folderCreated: "folderCreated", folderCreateRequested: "folderCreateRequested", folderRenamed: "folderRenamed", folderDeleted: "folderDeleted", testsMoved: "testsMoved", bulkActionClick: "bulkActionClick", bulkSelectAllChange: "bulkSelectAllChange", bulkDismiss: "bulkDismiss", bulkActionInvoked: "bulkActionInvoked", sidebarCollapsedChange: "sidebarCollapsedChange", subfolderSectionExpandedChange: "subfolderSectionExpandedChange", organizedSectionExpandedChange: "organizedSectionExpandedChange", reorderStart: "reorderStart", reorderCancel: "reorderCancel", reorderSave: "reorderSave", moveRequested: "moveRequested", selectedItemsChange: "selectedItemsChange", folderDeleteRequested: "folderDeleteRequested", folderMoveRequested: "folderMoveRequested", folderMoved: "folderMoved", folderDuplicateRequested: "folderDuplicateRequested", onReload: "onReload", onAutoRefreshClick: "onAutoRefreshClick", columnVisibilityChange: "columnVisibilityChange", autoRefreshIntervalChange: "autoRefreshIntervalChange" }, host: { classAttribute: "cqa-ui-root" }, viewQueries: [{ propertyName: "dynamicFilterComponent", first: true, predicate: DynamicFilterComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Reusable folder-icon chip. Render via <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>. -->\n<ng-template #folderIconChip let-color=\"color\">\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-10 cqa-h-10 cqa-rounded-lg cqa-bg-[#F0F0F1] cqa-text-[#6D6D74] cqa-flex-shrink-0 group-hover:cqa-bg-indigo-50 group-hover:cqa-text-[#3F43EE]\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" [attr.stroke]=\"color || 'currentColor'\" stroke-width=\"0.916667\"/>\n </svg>\n </span>\n</ng-template>\n\n<!-- Reusable indeterminate loading spinner. Render via\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 20 }\"></ng-container>. -->\n<ng-template #loadingSpinner let-size=\"size\">\n <svg [attr.width]=\"size || 20\" [attr.height]=\"size || 20\" viewBox=\"0 0 50 50\" aria-label=\"loading\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"25\" cy=\"25\" r=\"20\" stroke=\"#E5E7EB\" stroke-width=\"6\" fill=\"none\"></circle>\n <path d=\"M45 25a20 20 0 0 0-20-20\" stroke=\"#4F46E5\" stroke-width=\"6\" fill=\"none\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 25 25\" to=\"360 25 25\" dur=\"0.8s\" repeatCount=\"indefinite\"></animateTransform>\n </path>\n </svg>\n</ng-template>\n\n<!-- Reusable \"+\" chip used by the \"New folder\" tile. -->\n<ng-template #addIconChip>\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-10 cqa-h-10 cqa-rounded-lg cqa-bg-[#F0F0F1] cqa-text-[#6D6D74] cqa-flex-shrink-0 group-hover:cqa-bg-indigo-50 group-hover:cqa-text-[#3F43EE]\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M8 3V13M3 8H13\" stroke=\"currentColor\" stroke-width=\"1.275\" stroke-linecap=\"round\"/>\n </svg>\n </span>\n</ng-template>\n\n<div class=\"cqa-ui-root\">\n <div class=\"cqa-w-full cqa-flex cqa-flex-col cqa-relative\">\n <div [class]=\"!showSearchBar ? 'cqa-justify-end' : 'cqa-justify-between'\" class=\"cqa-w-full cqa-flex cqa-items-center cqa-gap-3 cqa-flex-wrap cqa-mb-3\">\n <cqa-search-bar\n *ngIf=\"showSearchBar\"\n [placeholder]=\"searchPlaceholder\"\n [value]=\"searchValue\"\n [showClear]=\"showClear\"\n (valueChange)=\"valueChange($event)\"\n (search)=\"search($event)\"\n (cleared)=\"cleared()\"\n ></cqa-search-bar>\n <div class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-flex-wrap\">\n <cqa-button\n *ngIf=\"showExportButton\"\n variant=\"grey-solid\"\n icon=\"open_in_new\"\n [text]=\"isExporting ? 'Exporting...' : 'Export'\"\n [disabled]=\"isExporting\"\n (clicked)=\"exportCodeClick()\"\n >\n <span>{{ isExporting ? 'Exporting...' : 'Export' }}</span>\n </cqa-button>\n \n <!-- Export Code Modal -->\n <cqa-export-code-modal\n *ngIf=\"showExportButton\"\n [isOpen]=\"isExportModalOpen\"\n [cases]=\"selectedCasesForExport\"\n [disabled]=\"false\"\n (closeModal)=\"closeExportModal()\"\n (export)=\"onExportModalExport($event)\">\n </cqa-export-code-modal>\n <ng-container *ngFor=\"let dropdownTemplate of otherDropDownButtons; trackBy: trackByDropdownTemplateRef\">\n <ng-container *ngTemplateOutlet=\"dropdownTemplate\"></ng-container>\n </ng-container>\n\n <ng-container *ngFor=\"let selectDropdownTemplate of otherSelectDropDownButtons; trackBy: trackBySelectDropdownTemplateRef\">\n <ng-container *ngTemplateOutlet=\"selectDropdownTemplate\"></ng-container>\n </ng-container>\n \n <cqa-button\n *ngIf=\"showFilterButton\"\n variant=\"grey-solid\"\n icon=\"add\"\n [disabled]=\"isReordering\"\n (clicked)=\"toggleFilter()\"\n >\n <span class=\"cqa-flex cqa-items-center cqa-gap-1\">\n Filter \n <div [class]=\"arrowClasses\">\n <svg\n class=\"cqa-w-2 cqa-h-1 cqa-absolute cqa-left-[4px] cqa-top-[6px]\"\n viewBox=\"0 0 8 4\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M0 0L4 4L8 0\"\n stroke=\"#0B0B0C\"\n stroke-width=\"1.33\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </div>\n </span>\n </cqa-button>\n <cqa-column-visibility\n *ngIf=\"showSettingsButton\"\n [columns]=\"visibilityColumns\"\n [columnVisibility]=\"columnVisibility\"\n [selectedAutoRefreshInterval]=\"selectedAutoRefreshInterval\"\n (columnVisibilityChange)=\"onColumnVisibilityChange($event)\"\n (autoRefreshChange)=\"onAutoRefreshChange($event)\"\n ></cqa-column-visibility>\n <cqa-button\n *ngIf=\"showAutoRefreshButton\"\n variant=\"grey-solid\"\n icon=\"refresh\"\n (clicked)=\"handleRefreshClick()\"\n [tooltip]=\"'Refresh'\"\n tooltipPosition=\"below\"\n [disabled]=\"isReordering\"\n ></cqa-button>\n <ng-container *ngFor=\"let buttonTemplate of otherButtons; trackBy: trackByTemplateRef\">\n <ng-container *ngTemplateOutlet=\"buttonTemplate\"></ng-container>\n </ng-container>\n <cqa-segment-control\n *ngIf=\"showViewModeToggle\"\n size=\"lg\"\n [segments]=\"viewModeSegments\"\n [value]=\"viewMode\"\n (valueChange)=\"onViewModeChange($event)\"\n ></cqa-segment-control>\n <cqa-button\n *ngIf=\"showReorderButton && !isReordering\"\n variant=\"outlined\"\n icon=\"drag_indicator\"\n [text]=\"reorderLabels.reorderButton\"\n [disabled]=\"!pagedRows || pagedRows.length === 0 || isTableLoading || isTableDataLoading\"\n (clicked)=\"startReorder()\"\n ></cqa-button>\n <ng-container *ngIf=\"showReorderButton && isReordering\">\n <cqa-button\n variant=\"outlined\"\n [text]=\"reorderLabels.cancelButton\"\n [disabled]=\"reorderSaving\"\n (clicked)=\"cancelReorder()\"\n ></cqa-button>\n <cqa-button\n variant=\"filled\"\n [icon]=\"reorderSaving ? 'hourglass_empty' : ''\"\n [text]=\"reorderSaving ? reorderLabels.savingButton : reorderLabels.doneButton\"\n [disabled]=\"reorderSaving\"\n (clicked)=\"saveReorder()\"\n ></cqa-button>\n </ng-container>\n </div>\n </div>\n\n <!-- Reorder mode banner -->\n <div *ngIf=\"isReordering\" class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-3 cqa-px-3 cqa-py-2 cqa-rounded-md cqa-bg-[#FFFBEB] cqa-border cqa-border-[#FDE68A]\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <circle cx=\"3\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"3\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"3\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n </svg>\n <span class=\"cqa-text-sm cqa-font-semibold cqa-text-[#92400E]\">{{ reorderLabels.bannerTitle }}</span>\n <span class=\"cqa-text-sm cqa-text-[#92400E]\">{{ reorderLabels.bannerDescription }}</span>\n </div>\n\n <cqa-selected-filters\n [filterApplied]=\"filterApplied\"\n [chips]=\"chips\"\n (removeChip)=\"onRemoveChip($event)\"\n (clearAll)=\"onClearAllChips()\"\n (onClearAll)=\"onClearAll.emit()\"\n >\n </cqa-selected-filters>\n\n <cqa-dynamic-filter\n *ngIf=\"showFilterPanel\"\n [config]=\"filterConfig\"\n [model]=\"filterModel\"\n [showFilterPanel]=\"showFilterPanel\"\n (filtersChanged)=\"onFiltersChanged($event)\"\n (filtersApplied)=\"onFiltersApplied($event)\"\n (onApplyFilterClick)=\"onApplyFilterClick.emit($event)\"\n (onResetFilterClick)=\"handleResetFilterClick()\"\n >\n </cqa-dynamic-filter>\n\n <div class=\"cqa-flex cqa-items-stretch cqa-gap-2 cqa-min-h-0 \" style=\"border-bottom: 1px solid rgb(226, 226, 227);\">\n <!-- Sidebar (only shown in modular view) -->\n <cqa-folder-sidebar\n *ngIf=\"isModularView && modularConfig.showSidebar\"\n [folders]=\"folders\"\n [selectedFolderId]=\"selectedFolderId\"\n [expandedFolderIds]=\"expandedFolderIds\"\n [unorganizedCount]=\"unorganizedCount\"\n [allowCreate]=\"modularConfig.allowCreateFolder\"\n [allowRename]=\"modularConfig.allowRenameFolder\"\n [allowDelete]=\"modularConfig.allowDeleteFolder\"\n [allowDrop]=\"modularConfig.allowTestDragDrop\"\n [showCounts]=\"modularConfig.showCounts\"\n [collapsed]=\"sidebarCollapsed\"\n [labels]=\"modularLabels\"\n [serverSideSearch]=\"serverSideSearch\"\n [rootTotal]=\"rootTotal\"\n [folderSearchLoading]=\"folderSearchLoading\"\n [rootFoldersLoading]=\"rootFoldersLoading\"\n [savingFolderIds]=\"savingFolderIds\"\n [searchValue]=\"folderSearchValue\"\n (folderSelected)=\"onFolderSelected($event)\"\n (folderExpansionToggled)=\"onFolderExpansionToggled($event)\"\n (folderChildrenRequested)=\"folderChildrenRequested.emit($event)\"\n (folderLoadMoreRequested)=\"folderLoadMoreRequested.emit($event)\"\n (rootLoadMoreRequested)=\"rootLoadMoreRequested.emit()\"\n (searchChange)=\"folderSearchChange.emit($event)\"\n (folderCreated)=\"onFolderCreated($event)\"\n (folderCreateRequested)=\"onFolderCreateRequested($event)\"\n (folderRenamed)=\"onFolderRenamed($event)\"\n (folderDeleted)=\"onFolderDeleted($event)\"\n (folderMoveRequested)=\"onFolderMoveRequested($event)\"\n (folderDuplicateRequested)=\"onFolderDuplicateRequested($event)\"\n (testsDropped)=\"onTestsDropped($event)\"\n (folderDropped)=\"onFolderDropped($event)\"\n (collapsedChange)=\"onSidebarCollapsedChange($event)\"\n ></cqa-folder-sidebar>\n\n <!-- Right pane -->\n <div\n class=\"cqa-flex-1 cqa-flex cqa-flex-col cqa-min-w-0\"\n [style.border-top]=\"isModularView ? '1px solid #E2E2E3' : null\"\n [style.border-left]=\"isModularView ? '1px solid #E2E2E3' : null\"\n [style.border-right]=\"isModularView ? '1px solid #E2E2E3' : null\"\n >\n <!-- Breadcrumb (modular view, folder drilled-in) -->\n <nav\n *ngIf=\"isModularView && modularConfig.showBreadcrumb && !isRootView\"\n aria-label=\"Folder path\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-text-sm cqa-px-3 cqa-py-2 cqa-rounded-none cqa-min-w-0\"\n style=\"color: #6D6D74; border-bottom: 1px solid #E2E2E3;\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-1 cqa-flex-1 cqa-min-w-0 cqa-overflow-x-auto cqa-scrollbar-thin cqa-whitespace-nowrap\">\n <button type=\"button\" class=\"hover:cqa-text-[#3F43EE] cqa-inline-flex cqa-items-center cqa-gap-1 cqa-flex-shrink-0\" (click)=\"onFolderSelected(null)\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" stroke=\"currentColor\" stroke-width=\"0.916667\"/>\n </svg>\n <span>{{ modularLabels.allFolders }}</span>\n </button>\n <ng-container *ngFor=\"let crumb of breadcrumbTrail; let last = last\">\n <mat-icon class=\"cqa-align-middle cqa-flex-shrink-0\" style=\"font-size:16px;width:16px;height:16px;color:#6D6D74;\">chevron_right</mat-icon>\n <button\n type=\"button\"\n class=\"cqa-flex-shrink-0\"\n [ngClass]=\"last\n ? 'cqa-text-[#161617] cqa-font-semibold'\n : 'cqa-text-[#6D6D74] hover:cqa-text-[#3F43EE]'\"\n [disabled]=\"last\"\n (click)=\"!last && onFolderSelected(crumb.id)\"\n >{{ crumb.name }}</button>\n </ng-container>\n </div>\n <cqa-button\n *ngIf=\"modularConfig.showSubfolderSection && subfolderTiles.length\"\n class=\"cqa-flex-shrink-0\"\n variant=\"text\"\n btnSize=\"sm\"\n customClass=\"!cqa-text-[13px] !cqa-leading-[18px]\"\n [icon]=\"subfolderSectionExpanded ? 'expand_less' : 'expand_more'\"\n [text]=\"(subfolderSectionExpanded ? 'Hide' : 'Show') + ' subfolders'\"\n [attr.aria-expanded]=\"subfolderSectionExpanded\"\n (clicked)=\"toggleSubfolderSection()\"\n ></cqa-button>\n <cqa-button\n *ngIf=\"modularConfig.allowCreateFolder\"\n class=\"cqa-flex-shrink-0\"\n variant=\"text\"\n btnSize=\"sm\"\n customClass=\"!cqa-text-[13px] !cqa-leading-[18px]\"\n icon=\"add\"\n [text]=\"modularLabels.newFolder\"\n (clicked)=\"onFolderCreateRequested({ parentId: selectedFolderId })\"\n ></cqa-button>\n </nav>\n\n <!-- Folder grid: modular + root view -->\n <section\n *ngIf=\"isModularView && isRootView && modularConfig.showFolderGrid && rootFolderTiles.length\"\n class=\"cqa-px-5 cqa-pt-2\"\n style=\"border-bottom: 1px solid #F0F0F1;\"\n >\n <button\n type=\"button\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-2 cqa-w-full cqa-text-left\"\n [attr.aria-expanded]=\"organizedSectionExpanded\"\n (click)=\"toggleOrganizedSection()\"\n >\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" stroke=\"#6D6D74\" stroke-width=\"0.916667\"/>\n </svg>\n <h3 class=\"cqa-text-sm cqa-font-semibold cqa-text-[#6D6D74]\">{{ modularLabels.organized }}</h3>\n <span class=\"cqa-text-xs cqa-text-[#6D6D74]\">\n {{ rootFolderTiles.length === 1 ? modularLabels.foldersCountSingular : modularLabels.foldersCountPlural.replace('{n}', '' + rootFolderTiles.length) }}\n </span>\n <mat-icon class=\"cqa-text-[#6D6D74] cqa-ml-auto\" style=\"font-size:18px;width:18px;height:18px\">\n {{ organizedSectionExpanded ? 'expand_less' : 'expand_more' }}\n </mat-icon>\n </button>\n <div\n class=\"cqa-grid\"\n style=\"transition: grid-template-rows 300ms cubic-bezier(0.4, 0, 0.2, 1);\"\n [style.grid-template-rows]=\"organizedSectionExpanded ? '1fr' : '0fr'\"\n >\n <div class=\"cqa-min-h-0\" style=\"overflow: hidden;\">\n <div class=\"cqa-flex cqa-flex-nowrap cqa-gap-3 cqa-overflow-x-auto cqa-scrollbar-thin cqa-pb-1\"\n (scroll)=\"onRootGridScroll($event)\">\n <button\n *ngFor=\"let f of rootFolderTiles\"\n type=\"button\"\n [cqaFolderDrop]=\"f.id\"\n [dropEnabled]=\"modularConfig.allowTestDragDrop\"\n (testsDropped)=\"onTestsDropped($event)\"\n (click)=\"onFolderSelected(f.id)\"\n class=\"cqa-group cqa-flex-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-justify-between cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-solid cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-white hover:cqa-border-[#3F43EE] hover:cqa-shadow-sm cqa-transition-colors\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-3 cqa-min-w-0\">\n <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ f.name }}</span>\n <span *ngIf=\"modularConfig.showCounts\" class=\"cqa-text-sm cqa-text-neutral-500 cqa-truncate\">\n {{ (f.count ?? 0) === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + (f.count ?? 0)) }}<ng-container *ngIf=\"f.children?.length\"> \u00B7 {{ f.children!.length === 1 ? modularLabels.oneSubfolder : modularLabels.subfoldersCount.replace('{n}', '' + f.children!.length) }}</ng-container>\n </span>\n </div>\n </div>\n <mat-icon class=\"cqa-text-neutral-400 cqa-flex-shrink-0\" style=\"font-size:20px;width:20px;height:20px\">chevron_right</mat-icon>\n </button>\n\n <!-- \"+ New folder\" tile -->\n <button\n *ngIf=\"modularConfig.allowCreateFolder\"\n type=\"button\"\n (click)=\"onFolderCreateRequested({ parentId: null })\"\n class=\"cqa-group cqa-flex-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-dashed cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-[#FAFAFA] hover:cqa-border-[#3F43EE] hover:cqa-bg-white cqa-transition-colors\"\n >\n <ng-container *ngTemplateOutlet=\"addIconChip\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ modularLabels.newFolder }}</span>\n <span class=\"cqa-text-sm cqa-text-neutral-400\">{{ modularLabels.testsCountPlural.replace('{n}', '0') }}</span>\n </div>\n </button>\n\n <!-- Pagination spinner anchored at the right edge of the Organized\n row. Only renders during an in-flight root fetch (initial OR\n scroll-driven \"load more\"), and is filtered to suppress when\n the section is empty so we don't show a spinner inside an\n otherwise-blank row. -->\n <div\n *ngIf=\"rootFoldersLoading && rootFolderTiles.length > 0\"\n class=\"cqa-flex-shrink-0 cqa-flex cqa-items-center cqa-justify-center cqa-w-[80px]\"\n aria-live=\"polite\"\n [attr.aria-label]=\"'Loading more folders'\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 20 }\"></ng-container>\n </div>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Subfolder section: modular + folder view. Shown whenever drilled in\n (even if no subfolders, so the \"+ New folder\" tile remains\n reachable, and so we can render a loading state during the\n drill-in fetch before children arrive). -->\n <section\n *ngIf=\"isModularView && !isRootView && modularConfig.showSubfolderSection && (subfolderTiles.length || currentFolderNode?.childrenLoading)\"\n class=\"cqa-grid\"\n style=\"transition: grid-template-rows 300ms cubic-bezier(0.4, 0, 0.2, 1);\"\n [style.grid-template-rows]=\"subfolderSectionExpanded ? '1fr' : '0fr'\"\n >\n <div class=\"cqa-min-h-0\" style=\"overflow: hidden;\">\n <div class=\"cqa-px-3 cqa-py-2 cqa-flex cqa-flex-nowrap cqa-gap-3 cqa-overflow-x-auto cqa-scrollbar-thin\"\n (scroll)=\"onSubfolderGridScroll($event)\">\n <button\n *ngFor=\"let f of subfolderTiles\"\n type=\"button\"\n [cqaFolderDrop]=\"f.id\"\n [dropEnabled]=\"modularConfig.allowTestDragDrop\"\n (testsDropped)=\"onTestsDropped($event)\"\n (click)=\"onFolderSelected(f.id)\"\n class=\"cqa-group cqa-flex-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-justify-between cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-solid cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-white hover:cqa-border-[#3F43EE] hover:cqa-shadow-sm cqa-transition-colors\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-3 cqa-min-w-0\">\n <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ f.name }}</span>\n <span *ngIf=\"modularConfig.showCounts\" class=\"cqa-text-sm cqa-text-neutral-500 cqa-truncate\">\n {{ (f.count ?? 0) === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + (f.count ?? 0)) }}\n </span>\n </div>\n </div>\n <mat-icon class=\"cqa-text-neutral-400 cqa-flex-shrink-0\" style=\"font-size:20px;width:20px;height:20px\">chevron_right</mat-icon>\n </button>\n\n <!-- \"+ New folder\" tile -->\n <button\n *ngIf=\"modularConfig.allowCreateFolder\"\n type=\"button\"\n (click)=\"onFolderCreateRequested({ parentId: selectedFolderId })\"\n class=\"cqa-group cqa-flex-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-dashed cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-[#FAFAFA] hover:cqa-border-[#3F43EE] hover:cqa-bg-white cqa-transition-colors\"\n >\n <ng-container *ngTemplateOutlet=\"addIconChip\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ modularLabels.newFolder }}</span>\n <span class=\"cqa-text-sm cqa-text-neutral-400\">{{ modularLabels.testsCountPlural.replace('{n}', '0') }}</span>\n </div>\n </button>\n\n <!-- Subfolder fetch indicator. Two cases:\n - Drill-in fetch: no subfolderTiles yet, render a centered\n row spinner so the section isn't empty during the fetch.\n - Pagination fetch: tiles exist, anchor a small spinner at\n the right edge so the user knows the next page is loading. -->\n <div\n *ngIf=\"currentFolderNode?.childrenLoading\"\n class=\"cqa-flex-shrink-0 cqa-flex cqa-items-center cqa-justify-center cqa-w-[80px]\"\n aria-live=\"polite\"\n [attr.aria-label]=\"'Loading subfolders'\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 20 }\"></ng-container>\n </div>\n </div>\n </div>\n </section>\n\n <div\n *ngIf=\"isModularView && !isRootView && modularConfig.showUnorganizedSection\"\n class=\"cqa-flex cqa-items-center cqa-justify-between cqa-px-4 cqa-py-3 cqa-border cqa-border-indigo-100\"\n style=\"background-color: #D8D9FC;\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-2\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9585 10.4585C12.9585 10.79 12.8268 11.108 12.5924 11.3424C12.358 11.5768 12.04 11.7085 11.7085 11.7085H1.7085C1.37698 11.7085 1.05903 11.5768 0.824613 11.3424C0.590192 11.108 0.458496 10.79 0.458496 10.4585V1.7085C0.458496 1.37698 0.590192 1.05903 0.824613 0.824613C1.05903 0.590192 1.37698 0.458496 1.7085 0.458496H4.8335L6.0835 2.3335H11.7085C12.04 2.3335 12.358 2.46519 12.5924 2.69961C12.8268 2.93403 12.9585 3.25198 12.9585 3.5835V10.4585Z\" stroke=\"#3F43EE\" stroke-width=\"0.916667\"/>\n </svg>\n <span class=\"cqa-text-sm cqa-font-semibold\" style=\"color: #3F43EE;\">\n {{ currentFolderNode?.name }} ({{ currentFolderDirectCount }})\n </span>\n </div>\n <cqa-button\n variant=\"outlined\"\n btnSize=\"sm\"\n customClass=\"!cqa-text-[13px]\"\n inlineStyles=\"border-color: #3F43EE; color: #3F43EE;\"\n [text]=\"modularLabels.clearFilter\"\n (clicked)=\"onFolderSelected(null)\"\n ></cqa-button>\n </div>\n\n <!-- Unorganized section header at root view -->\n <div\n *ngIf=\"isModularView && isRootView && modularConfig.showUnorganizedSection\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-2 cqa-mt-2\" style=\"margin-left: 20px;\"\n >\n <mat-icon class=\"cqa-text-[#6D6D74]\" style=\"font-size:16px;width:16px;height:16px\">inbox</mat-icon>\n <h3 class=\"cqa-text-sm cqa-font-semibold cqa-text-[#6D6D74]\">{{ modularLabels.unorganized }}</h3>\n <span class=\"cqa-text-xs cqa-text-[#6D6D74]\">\n {{ unorganizedCount === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + unorganizedCount) }}\n </span>\n </div>\n\n <div class=\"cqa-overflow-hidden cqa-border-t cqa-border-l cqa-border-r cqa-border-grey-200 cqa-relative cqa-overflow-x-auto\"\n [class.cqa-modular-table-flush]=\"isModularView\"\n [attr.style]=\"(!isModularView ? 'border-radius: 7px;' : '') + ((pagedRows && pagedRows.length > 0) ? '' : ' border-bottom: none !important;')\">\n <ng-container *ngIf=\"(isTableLoading || isTableDataLoading) || (!effectiveIsEmptyState && pagedRows && pagedRows.length > 0); else storyEmptyTpl\">\n <app-dynamic-table\n [style.border-bottom]=\"pagedRows && pagedRows.length > 0 ? '1px solid rgb(226, 226, 227)' : null\"\n [columns]=\"computedColumns\"\n [data]=\"pagedRows\"\n [rowSelectable]=\"rowSelectable\"\n [enableLocalSort]=\"enableLocalSort && !isReordering\"\n [isTableLoading]=\"isTableLoading\"\n [isTableDataLoading]=\"isTableDataLoading\"\n [cellJsonPathGetter]=\"cellJsonPathGetter\"\n [onJsonPathCopiedHandler]=\"onJsonPathCopiedHandler\"\n [enableRowReorder]=\"isReordering\"\n (rowReorder)=\"onRowReorder($event)\"\n (sortChange)=\"sortChange.emit($event)\">\n <ng-template #emptyTableTpl>\n <div class=\"cqa-flex cqa-flex-col cqa-items-center cqa-justify-center cqa-py-8\">\n <img src=\"/assets/illustrations/empty-state.svg\" alt=\"No data\" class=\"cqa-w-32 cqa-h-32 cqa-mb-4\" />\n <h3 class=\"cqa-text-lg cqa-font-semibold cqa-mb-2\">No test cases</h3>\n <p class=\"cqa-text-sm cqa-text-neutral-500 cqa-mb-4\">Try adjusting filters or create a new test case.</p>\n <cqa-button variant=\"filled\" (clicked)=\"toggleFilter()\">Show Filters</cqa-button>\n </div>\n </ng-template>\n </app-dynamic-table>\n </ng-container>\n\n <ng-template #storyEmptyTpl>\n <div class=\"cqa-p-6 cqa-flex cqa-flex-col cqa-items-center cqa-justify-center\">\n <cqa-empty-state\n *ngIf=\"effectiveIsEmptyState\"\n [title]=\"effectiveEmptyStateConfig.title\"\n [description]=\"effectiveEmptyStateConfig.description\"\n [imageUrl]=\"effectiveEmptyStateConfig.imageUrl\"\n [actions]=\"effectiveEmptyStateConfig.actions\"\n (actionClick)=\"onEmptyAction($event)\"\n >\n </cqa-empty-state>\n </div>\n </ng-template>\n </div>\n\n <cqa-pagination\n [totalElements]=\"serverSidePagination ? totalElements : filteredRows.length\"\n [pageIndex]=\"pageIndex\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [pageSizeMenuDirection]=\"pageSizeMenuDirection\"\n [pageItemCount]=\"pagedRows.length\"\n (paginate)=\"onPaginate($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\"\n >\n </cqa-pagination>\n </div>\n </div>\n\n <!-- Reused bulk action toolbar -->\n <div *ngIf=\"anyRowSelected && modularConfig.allowBulkSelection && !isReordering\" class=\"cqa-absolute cqa-bottom-[18.75px] cqa-left-[50%] cqa-translate-x-[-50%] cqa-w-full lg:cqa-max-w-[68%] cqa-sm:max-w-[75%] cqa-max-w-[90%] cqa-z-[1]\">\n <cqa-table-action-toolbar\n [selectedItems]=\"selectedItems && selectedItems.length > 0 ? selectedItems : currentSelectedItems\"\n [actions]=\"effectiveBulkActions\"\n [showSelectAll]=\"showSelectAllInToolbar\"\n [allSelected]=\"allSelectedInToolbar\"\n [showDismiss]=\"showDismissInToolbar\"\n (actionClick)=\"onBulkAction($event)\"\n (selectAllChange)=\"onBulkSelectAll($event)\"\n (dismiss)=\"onBulkDismiss()\"\n ></cqa-table-action-toolbar>\n </div>\n\n </div>\n</div>\n\n", components: [{ type: SearchBarComponent, selector: "cqa-search-bar", inputs: ["placeholder", "value", "disabled", "showClear", "ariaLabel", "autoFocus", "size", "fullWidth"], outputs: ["valueChange", "search", "cleared"] }, { type: ButtonComponent, selector: "cqa-button", inputs: ["variant", "btnSize", "disabled", "loading", "icon", "iconPosition", "fullWidth", "iconColor", "type", "text", "customClass", "inlineStyles", "tooltip", "tooltipPosition"], outputs: ["clicked"] }, { type: ExportCodeModalComponent, selector: "cqa-export-code-modal", inputs: ["isOpen", "cases", "disabled"], outputs: ["closeModal", "export"] }, { type: ColumnVisibilityComponent, selector: "cqa-column-visibility", inputs: ["isStepGroup", "columns", "columnVisibility", "selectedAutoRefreshInterval"], outputs: ["columnVisibilityChange", "autoRefreshChange"] }, { type: SegmentControlComponent, selector: "cqa-segment-control", inputs: ["segments", "value", "disabled", "containerBgColor", "fullWidth", "size"], outputs: ["valueChange"] }, { type: SelectedFiltersComponent, selector: "cqa-selected-filters", inputs: ["filterApplied", "chips", "defaultChips", "defaultChipClass"], outputs: ["removeChip", "clearAll", "onClearAll"] }, { type: DynamicFilterComponent, selector: "cqa-dynamic-filter", inputs: ["config", "model", "showFilterPanel", "buttonLayout"], outputs: ["filtersApplied", "filtersChanged", "resetAction", "onApplyFilterClick", "onResetFilterClick"] }, { type: FolderSidebarComponent, selector: "cqa-folder-sidebar", inputs: ["folders", "selectedFolderId", "expandedFolderIds", "unorganizedCount", "allowCreate", "allowRename", "allowDelete", "allowMove", "allowDuplicate", "allowDrop", "showCounts", "collapsed", "foldersAccordionExpanded", "labels", "serverSideSearch", "rootTotal", "folderSearchLoading", "rootFoldersLoading", "savingFolderIds", "searchValue"], outputs: ["folderSelected", "folderExpansionToggled", "folderChildrenRequested", "folderLoadMoreRequested", "searchChange", "rootLoadMoreRequested", "folderCreated", "folderCreateRequested", "folderRenamed", "folderDeleted", "folderMoveRequested", "folderDuplicateRequested", "testsDropped", "folderDropped", "collapsedChange", "foldersAccordionExpandedChange"] }, { type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { type: DynamicTableComponent, selector: "app-dynamic-table", inputs: ["data", "columns", "emptyState", "gridTemplateColumns", "screenWidth", "enableSelectAll", "rowSelectable", "enableLocalSort", "isTableLoading", "isTableDataLoading", "cellJsonPathGetter", "onJsonPathCopiedHandler", "enableRowReorder", "reorderHandleTooltip"], outputs: ["sortChange", "rowReorder"] }, { type: EmptyStateComponent, selector: "cqa-empty-state", inputs: ["preset", "imageUrl", "title", "description", "actions"], outputs: ["actionClick"] }, { type: PaginationComponent, selector: "cqa-pagination", inputs: ["totalElements", "totalPages", "pageIndex", "pageSize", "pageItemCount", "pageSizeOptions", "pageSizeMenuDirection"], outputs: ["pageIndexChange", "pageSizeChange", "paginate"] }, { type: TableActionToolbarComponent, selector: "cqa-table-action-toolbar", inputs: ["selectedItems", "actions", "showSelectAll", "allSelected", "showDismiss"], outputs: ["actionClick", "selectAllChange", "dismiss"] }], directives: [{ type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }, { type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { type: FolderDropDirective, selector: "[cqaFolderDrop]", inputs: ["cqaFolderDrop", "dropEnabled"], outputs: ["testsDropped", "folderDropped"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11087
11206
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: ModularTableTemplateComponent, decorators: [{
11088
11207
  type: Component,
11089
- args: [{ selector: 'cqa-modular-table-template', host: { class: 'cqa-ui-root' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Reusable folder-icon chip. Render via <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>. -->\n<ng-template #folderIconChip let-color=\"color\">\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-10 cqa-h-10 cqa-rounded-lg cqa-bg-[#F0F0F1] cqa-text-[#6D6D74] cqa-shrink-0 group-hover:cqa-bg-indigo-50 group-hover:cqa-text-[#3F43EE]\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" [attr.stroke]=\"color || 'currentColor'\" stroke-width=\"0.916667\"/>\n </svg>\n </span>\n</ng-template>\n\n<!-- Reusable \"+\" chip used by the \"New folder\" tile. -->\n<ng-template #addIconChip>\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-10 cqa-h-10 cqa-rounded-lg cqa-bg-[#F0F0F1] cqa-text-[#6D6D74] cqa-shrink-0 group-hover:cqa-bg-indigo-50 group-hover:cqa-text-[#3F43EE]\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M8 3V13M3 8H13\" stroke=\"currentColor\" stroke-width=\"1.275\" stroke-linecap=\"round\"/>\n </svg>\n </span>\n</ng-template>\n\n<div class=\"cqa-ui-root\">\n <div class=\"cqa-w-full cqa-flex cqa-flex-col cqa-relative\">\n <div [class]=\"!showSearchBar ? 'cqa-justify-end' : 'cqa-justify-between'\" class=\"cqa-w-full cqa-flex cqa-items-center cqa-gap-3 cqa-flex-wrap cqa-mb-3\">\n <cqa-search-bar\n *ngIf=\"showSearchBar\"\n [placeholder]=\"searchPlaceholder\"\n [value]=\"searchValue\"\n [showClear]=\"showClear\"\n (valueChange)=\"valueChange($event)\"\n (search)=\"search($event)\"\n (cleared)=\"cleared()\"\n ></cqa-search-bar>\n <div class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-flex-wrap\">\n <cqa-button\n *ngIf=\"showExportButton\"\n variant=\"grey-solid\"\n icon=\"open_in_new\"\n [text]=\"isExporting ? 'Exporting...' : 'Export'\"\n [disabled]=\"isExporting\"\n (clicked)=\"exportCodeClick()\"\n >\n <span>{{ isExporting ? 'Exporting...' : 'Export' }}</span>\n </cqa-button>\n \n <!-- Export Code Modal -->\n <cqa-export-code-modal\n *ngIf=\"showExportButton\"\n [isOpen]=\"isExportModalOpen\"\n [cases]=\"selectedCasesForExport\"\n [disabled]=\"false\"\n (closeModal)=\"closeExportModal()\"\n (export)=\"onExportModalExport($event)\">\n </cqa-export-code-modal>\n <ng-container *ngFor=\"let dropdownTemplate of otherDropDownButtons; trackBy: trackByDropdownTemplateRef\">\n <ng-container *ngTemplateOutlet=\"dropdownTemplate\"></ng-container>\n </ng-container>\n\n <ng-container *ngFor=\"let selectDropdownTemplate of otherSelectDropDownButtons; trackBy: trackBySelectDropdownTemplateRef\">\n <ng-container *ngTemplateOutlet=\"selectDropdownTemplate\"></ng-container>\n </ng-container>\n \n <cqa-button\n *ngIf=\"showFilterButton\"\n variant=\"grey-solid\"\n icon=\"add\"\n [disabled]=\"isReordering\"\n (clicked)=\"toggleFilter()\"\n >\n <span class=\"cqa-flex cqa-items-center cqa-gap-1\">\n Filter \n <div [class]=\"arrowClasses\">\n <svg\n class=\"cqa-w-2 cqa-h-1 cqa-absolute cqa-left-[4px] cqa-top-[6px]\"\n viewBox=\"0 0 8 4\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M0 0L4 4L8 0\"\n stroke=\"#0B0B0C\"\n stroke-width=\"1.33\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </div>\n </span>\n </cqa-button>\n <cqa-column-visibility\n *ngIf=\"showSettingsButton\"\n [columns]=\"visibilityColumns\"\n [columnVisibility]=\"columnVisibility\"\n [selectedAutoRefreshInterval]=\"selectedAutoRefreshInterval\"\n (columnVisibilityChange)=\"onColumnVisibilityChange($event)\"\n (autoRefreshChange)=\"onAutoRefreshChange($event)\"\n ></cqa-column-visibility>\n <cqa-button\n *ngIf=\"showAutoRefreshButton\"\n variant=\"grey-solid\"\n icon=\"refresh\"\n (clicked)=\"handleRefreshClick()\"\n [tooltip]=\"'Refresh'\"\n tooltipPosition=\"below\"\n [disabled]=\"isReordering\"\n ></cqa-button>\n <ng-container *ngFor=\"let buttonTemplate of otherButtons; trackBy: trackByTemplateRef\">\n <ng-container *ngTemplateOutlet=\"buttonTemplate\"></ng-container>\n </ng-container>\n <cqa-segment-control\n *ngIf=\"showViewModeToggle\"\n size=\"lg\"\n [segments]=\"viewModeSegments\"\n [value]=\"viewMode\"\n (valueChange)=\"onViewModeChange($event)\"\n ></cqa-segment-control>\n <cqa-button\n *ngIf=\"showReorderButton && !isReordering\"\n variant=\"outlined\"\n icon=\"drag_indicator\"\n [text]=\"reorderLabels.reorderButton\"\n [disabled]=\"!pagedRows || pagedRows.length === 0 || isTableLoading || isTableDataLoading\"\n (clicked)=\"startReorder()\"\n ></cqa-button>\n <ng-container *ngIf=\"showReorderButton && isReordering\">\n <cqa-button\n variant=\"outlined\"\n [text]=\"reorderLabels.cancelButton\"\n [disabled]=\"reorderSaving\"\n (clicked)=\"cancelReorder()\"\n ></cqa-button>\n <cqa-button\n variant=\"filled\"\n [icon]=\"reorderSaving ? 'hourglass_empty' : ''\"\n [text]=\"reorderSaving ? reorderLabels.savingButton : reorderLabels.doneButton\"\n [disabled]=\"reorderSaving\"\n (clicked)=\"saveReorder()\"\n ></cqa-button>\n </ng-container>\n </div>\n </div>\n\n <!-- Reorder mode banner -->\n <div *ngIf=\"isReordering\" class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-3 cqa-px-3 cqa-py-2 cqa-rounded-md cqa-bg-[#FFFBEB] cqa-border cqa-border-[#FDE68A]\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <circle cx=\"3\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"3\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"3\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n </svg>\n <span class=\"cqa-text-sm cqa-font-semibold cqa-text-[#92400E]\">{{ reorderLabels.bannerTitle }}</span>\n <span class=\"cqa-text-sm cqa-text-[#92400E]\">{{ reorderLabels.bannerDescription }}</span>\n </div>\n\n <cqa-selected-filters\n [filterApplied]=\"filterApplied\"\n [chips]=\"chips\"\n (removeChip)=\"onRemoveChip($event)\"\n (clearAll)=\"onClearAllChips()\"\n (onClearAll)=\"onClearAll.emit()\"\n >\n </cqa-selected-filters>\n\n <cqa-dynamic-filter\n *ngIf=\"showFilterPanel\"\n [config]=\"filterConfig\"\n [model]=\"filterModel\"\n [showFilterPanel]=\"showFilterPanel\"\n (filtersChanged)=\"onFiltersChanged($event)\"\n (filtersApplied)=\"onFiltersApplied($event)\"\n (onApplyFilterClick)=\"onApplyFilterClick.emit($event)\"\n (onResetFilterClick)=\"handleResetFilterClick()\"\n >\n </cqa-dynamic-filter>\n\n <div class=\"cqa-flex cqa-items-stretch cqa-gap-3 cqa-min-h-0 \" style=\"border-bottom: 1px solid rgb(226, 226, 227);\">\n <!-- Sidebar (only shown in modular view) -->\n <cqa-folder-sidebar\n *ngIf=\"isModularView && modularConfig.showSidebar\"\n [folders]=\"folders\"\n [selectedFolderId]=\"selectedFolderId\"\n [expandedFolderIds]=\"expandedFolderIds\"\n [unorganizedCount]=\"unorganizedCount\"\n [allowCreate]=\"modularConfig.allowCreateFolder\"\n [allowRename]=\"modularConfig.allowRenameFolder\"\n [allowDelete]=\"modularConfig.allowDeleteFolder\"\n [allowDrop]=\"modularConfig.allowTestDragDrop\"\n [showCounts]=\"modularConfig.showCounts\"\n [collapsed]=\"sidebarCollapsed\"\n [labels]=\"modularLabels\"\n [serverSideSearch]=\"serverSideSearch\"\n [rootTotal]=\"rootTotal\"\n [folderSearchLoading]=\"folderSearchLoading\"\n [rootFoldersLoading]=\"rootFoldersLoading\"\n [searchValue]=\"folderSearchValue\"\n (folderSelected)=\"onFolderSelected($event)\"\n (folderExpansionToggled)=\"onFolderExpansionToggled($event)\"\n (folderChildrenRequested)=\"folderChildrenRequested.emit($event)\"\n (folderLoadMoreRequested)=\"folderLoadMoreRequested.emit($event)\"\n (rootLoadMoreRequested)=\"rootLoadMoreRequested.emit()\"\n (searchChange)=\"folderSearchChange.emit($event)\"\n (folderCreated)=\"onFolderCreated($event)\"\n (folderCreateRequested)=\"onFolderCreateRequested($event)\"\n (folderRenamed)=\"onFolderRenamed($event)\"\n (folderDeleted)=\"onFolderDeleted($event)\"\n (folderMoveRequested)=\"onFolderMoveRequested($event)\"\n (folderDuplicateRequested)=\"onFolderDuplicateRequested($event)\"\n (testsDropped)=\"onTestsDropped($event)\"\n (folderDropped)=\"onFolderDropped($event)\"\n (collapsedChange)=\"onSidebarCollapsedChange($event)\"\n ></cqa-folder-sidebar>\n\n <!-- Right pane -->\n <div\n class=\"cqa-flex-1 cqa-flex cqa-flex-col cqa-min-w-0\"\n [style.border-top]=\"isModularView ? '1px solid #E2E2E3' : null\"\n [style.border-left]=\"isModularView ? '1px solid #E2E2E3' : null\"\n [style.border-right]=\"isModularView ? '1px solid #E2E2E3' : null\"\n >\n <!-- Breadcrumb (modular view, folder drilled-in) -->\n <nav\n *ngIf=\"isModularView && modularConfig.showBreadcrumb && !isRootView\"\n aria-label=\"Folder path\"\n class=\"cqa-flex cqa-items-center cqa-gap-1 cqa-text-sm cqa-px-3 cqa-py-1 cqa-mb-3 cqa-rounded-none\"\n style=\"color: #6D6D74; border-bottom: 1px solid #E2E2E3;\"\n >\n <button type=\"button\" class=\"hover:cqa-text-[#3F43EE] cqa-inline-flex cqa-items-center cqa-gap-1\" (click)=\"onFolderSelected(null)\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" stroke=\"currentColor\" stroke-width=\"0.916667\"/>\n </svg>\n <span>{{ modularLabels.allFolders }}</span>\n </button>\n <ng-container *ngFor=\"let crumb of breadcrumbTrail; let last = last\">\n <mat-icon class=\"cqa-align-middle\" style=\"font-size:16px;width:16px;height:16px;color:#6D6D74;\">chevron_right</mat-icon>\n <button\n type=\"button\"\n [ngClass]=\"last\n ? 'cqa-text-[#161617] cqa-font-semibold'\n : 'cqa-text-[#6D6D74] hover:cqa-text-[#3F43EE]'\"\n [disabled]=\"last\"\n (click)=\"!last && onFolderSelected(crumb.id)\"\n >{{ crumb.name }}</button>\n </ng-container>\n </nav>\n\n <!-- Folder grid: modular + root view -->\n <section\n *ngIf=\"isModularView && isRootView && modularConfig.showFolderGrid && rootFolderTiles.length\"\n class=\"cqa-mb-4 cqa-px-5 cqa-pt-4 cqa-pb-[14px]\"\n style=\"border-bottom: 1px solid #F0F0F1;\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-2\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" stroke=\"#6D6D74\" stroke-width=\"0.916667\"/>\n </svg>\n <h3 class=\"cqa-text-sm cqa-font-semibold cqa-text-[#6D6D74]\">{{ modularLabels.organized }}</h3>\n <span class=\"cqa-text-xs cqa-text-[#6D6D74]\">\n {{ rootFolderTiles.length === 1 ? modularLabels.foldersCountSingular : modularLabels.foldersCountPlural.replace('{n}', '' + rootFolderTiles.length) }}\n </span>\n </div>\n <div class=\"cqa-flex cqa-flex-nowrap cqa-gap-3 cqa-overflow-x-auto cqa-scrollbar-thin cqa-pb-2\"\n (scroll)=\"onRootGridScroll($event)\">\n <button\n *ngFor=\"let f of rootFolderTiles\"\n type=\"button\"\n [cqaFolderDrop]=\"f.id\"\n [dropEnabled]=\"modularConfig.allowTestDragDrop\"\n (testsDropped)=\"onTestsDropped($event)\"\n (click)=\"onFolderSelected(f.id)\"\n class=\"cqa-group cqa-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-justify-between cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-solid cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-white hover:cqa-border-[#3F43EE] hover:cqa-shadow-sm cqa-transition-colors\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-3 cqa-min-w-0\">\n <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ f.name }}</span>\n <span *ngIf=\"modularConfig.showCounts\" class=\"cqa-text-sm cqa-text-neutral-500 cqa-truncate\">\n {{ (f.count ?? 0) === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + (f.count ?? 0)) }}<ng-container *ngIf=\"f.children?.length\"> \u00B7 {{ f.children!.length === 1 ? modularLabels.oneSubfolder : modularLabels.subfoldersCount.replace('{n}', '' + f.children!.length) }}</ng-container>\n </span>\n </div>\n </div>\n <mat-icon class=\"cqa-text-neutral-400 cqa-shrink-0\" style=\"font-size:20px;width:20px;height:20px\">chevron_right</mat-icon>\n </button>\n\n <!-- \"+ New folder\" tile -->\n <button\n *ngIf=\"modularConfig.allowCreateFolder\"\n type=\"button\"\n (click)=\"onFolderCreateRequested({ parentId: null })\"\n class=\"cqa-group cqa-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-dashed cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-[#FAFAFA] hover:cqa-border-[#3F43EE] hover:cqa-bg-white cqa-transition-colors\"\n >\n <ng-container *ngTemplateOutlet=\"addIconChip\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ modularLabels.newFolder }}</span>\n <span class=\"cqa-text-sm cqa-text-neutral-400\">{{ modularLabels.testsCountPlural.replace('{n}', '0') }}</span>\n </div>\n </button>\n\n <!-- Pagination spinner anchored at the right edge of the Organized\n row. Only renders during an in-flight root fetch (initial OR\n scroll-driven \"load more\"), and is filtered to suppress when\n the section is empty so we don't show a spinner inside an\n otherwise-blank row. -->\n <div\n *ngIf=\"rootFoldersLoading && rootFolderTiles.length > 0\"\n class=\"cqa-shrink-0 cqa-flex cqa-items-center cqa-justify-center cqa-w-[80px]\"\n aria-live=\"polite\"\n [attr.aria-label]=\"'Loading more folders'\"\n >\n <mat-spinner diameter=\"20\" mode=\"indeterminate\"></mat-spinner>\n </div>\n </div>\n </section>\n\n <!-- Subfolder section: modular + folder view. Shown whenever drilled in\n (even if no subfolders, so the \"+ New folder\" tile remains\n reachable, and so we can render a loading state during the\n drill-in fetch before children arrive). -->\n <section\n *ngIf=\"isModularView && !isRootView && modularConfig.showSubfolderSection && (subfolderTiles.length || currentFolderNode?.childrenLoading)\"\n class=\"cqa-mb-4 cqa-px-3 cqa-py-1\"\n >\n <div *ngIf=\"subfolderTiles.length || modularConfig.allowCreateFolder\" class=\"cqa-text-sm cqa-text-neutral-600 cqa-mb-2\">\n {{ modularLabels.subfoldersIn }} {{ breadcrumbTrail.length ? breadcrumbTrail[breadcrumbTrail.length - 1].name : '' }}\n </div>\n <div class=\"cqa-flex cqa-flex-nowrap cqa-gap-3 cqa-overflow-x-auto cqa-scrollbar-thin cqa-pb-2\"\n (scroll)=\"onSubfolderGridScroll($event)\">\n <button\n *ngFor=\"let f of subfolderTiles\"\n type=\"button\"\n [cqaFolderDrop]=\"f.id\"\n [dropEnabled]=\"modularConfig.allowTestDragDrop\"\n (testsDropped)=\"onTestsDropped($event)\"\n (click)=\"onFolderSelected(f.id)\"\n class=\"cqa-group cqa-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-justify-between cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-solid cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-white hover:cqa-border-[#3F43EE] hover:cqa-shadow-sm cqa-transition-colors\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-3 cqa-min-w-0\">\n <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ f.name }}</span>\n <span *ngIf=\"modularConfig.showCounts\" class=\"cqa-text-sm cqa-text-neutral-500 cqa-truncate\">\n {{ (f.count ?? 0) === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + (f.count ?? 0)) }}\n </span>\n </div>\n </div>\n <mat-icon class=\"cqa-text-neutral-400 cqa-shrink-0\" style=\"font-size:20px;width:20px;height:20px\">chevron_right</mat-icon>\n </button>\n\n <!-- \"+ New folder\" tile -->\n <button\n *ngIf=\"modularConfig.allowCreateFolder\"\n type=\"button\"\n (click)=\"onFolderCreateRequested({ parentId: selectedFolderId })\"\n class=\"cqa-group cqa-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-dashed cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-[#FAFAFA] hover:cqa-border-[#3F43EE] hover:cqa-bg-white cqa-transition-colors\"\n >\n <ng-container *ngTemplateOutlet=\"addIconChip\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ modularLabels.newFolder }}</span>\n <span class=\"cqa-text-sm cqa-text-neutral-400\">{{ modularLabels.testsCountPlural.replace('{n}', '0') }}</span>\n </div>\n </button>\n\n <!-- Subfolder fetch indicator. Two cases:\n - Drill-in fetch: no subfolderTiles yet, render a centered\n row spinner so the section isn't empty during the fetch.\n - Pagination fetch: tiles exist, anchor a small spinner at\n the right edge so the user knows the next page is loading. -->\n <div\n *ngIf=\"currentFolderNode?.childrenLoading\"\n class=\"cqa-shrink-0 cqa-flex cqa-items-center cqa-justify-center cqa-w-[80px]\"\n aria-live=\"polite\"\n [attr.aria-label]=\"'Loading subfolders'\"\n >\n <mat-spinner diameter=\"20\" mode=\"indeterminate\"></mat-spinner>\n </div>\n </div>\n </section>\n\n <!-- Folder panel header above the table: \"Hotel Search (3)\" + Clear filter -->\n <div\n *ngIf=\"isModularView && !isRootView && modularConfig.showUnorganizedSection\"\n class=\"cqa-flex cqa-mb-1 cqa-items-center cqa-justify-between cqa-px-4 cqa-py-3 cqa-rounded-t-lg cqa-bg-indigo-50 cqa-border cqa-border-indigo-100\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-2\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9585 10.4585C12.9585 10.79 12.8268 11.108 12.5924 11.3424C12.358 11.5768 12.04 11.7085 11.7085 11.7085H1.7085C1.37698 11.7085 1.05903 11.5768 0.824613 11.3424C0.590192 11.108 0.458496 10.79 0.458496 10.4585V1.7085C0.458496 1.37698 0.590192 1.05903 0.824613 0.824613C1.05903 0.590192 1.37698 0.458496 1.7085 0.458496H4.8335L6.0835 2.3335H11.7085C12.04 2.3335 12.358 2.46519 12.5924 2.69961C12.8268 2.93403 12.9585 3.25198 12.9585 3.5835V10.4585Z\" [attr.stroke]=\"currentFolderNode?.color || '#3F43EE'\" stroke-width=\"0.916667\"/>\n </svg>\n <span class=\"cqa-text-sm cqa-font-semibold cqa-text-indigo-700\">\n {{ currentFolderNode?.name }} ({{ currentFolderDirectCount }})\n </span>\n </div>\n <button\n type=\"button\"\n class=\"cqa-px-3 cqa-py-1 cqa-text-xs cqa-font-medium cqa-text-indigo-700 cqa-bg-white cqa-border cqa-border-indigo-200 cqa-rounded hover:cqa-bg-indigo-50\"\n (click)=\"onFolderSelected(null)\"\n >{{ modularLabels.clearFilter }}</button>\n </div>\n\n <!-- Unorganized section header at root view -->\n <div\n *ngIf=\"isModularView && isRootView && modularConfig.showUnorganizedSection\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-2 cqa-mt-2\" style=\"margin-left: 20px;\"\n >\n <mat-icon class=\"cqa-text-[#6D6D74]\" style=\"font-size:16px;width:16px;height:16px\">inbox</mat-icon>\n <h3 class=\"cqa-text-sm cqa-font-semibold cqa-text-[#6D6D74]\">{{ modularLabels.unorganized }}</h3>\n <span class=\"cqa-text-xs cqa-text-[#6D6D74]\">\n {{ unorganizedCount === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + unorganizedCount) }}\n </span>\n </div>\n\n <!-- Table -->\n <div class=\"cqa-rounded-[7px] cqa-overflow-hidden cqa-border-t cqa-border-l cqa-border-r cqa-border-grey-200 cqa-relative cqa-overflow-x-auto\"\n [attr.style]=\"pagedRows && pagedRows.length > 0 ? null : 'border-bottom: none !important'\">\n <ng-container *ngIf=\"(isTableLoading || isTableDataLoading) || (!effectiveIsEmptyState && pagedRows && pagedRows.length > 0); else storyEmptyTpl\">\n <app-dynamic-table\n [style.border-bottom]=\"pagedRows && pagedRows.length > 0 ? '1px solid rgb(226, 226, 227)' : null\"\n [columns]=\"computedColumns\"\n [data]=\"pagedRows\"\n [rowSelectable]=\"rowSelectable\"\n [enableLocalSort]=\"enableLocalSort && !isReordering\"\n [isTableLoading]=\"isTableLoading\"\n [isTableDataLoading]=\"isTableDataLoading\"\n [cellJsonPathGetter]=\"cellJsonPathGetter\"\n [onJsonPathCopiedHandler]=\"onJsonPathCopiedHandler\"\n [enableRowReorder]=\"isReordering\"\n (rowReorder)=\"onRowReorder($event)\"\n (sortChange)=\"sortChange.emit($event)\">\n <ng-template #emptyTableTpl>\n <div class=\"cqa-flex cqa-flex-col cqa-items-center cqa-justify-center cqa-py-8\">\n <img src=\"/assets/illustrations/empty-state.svg\" alt=\"No data\" class=\"cqa-w-32 cqa-h-32 cqa-mb-4\" />\n <h3 class=\"cqa-text-lg cqa-font-semibold cqa-mb-2\">No test cases</h3>\n <p class=\"cqa-text-sm cqa-text-neutral-500 cqa-mb-4\">Try adjusting filters or create a new test case.</p>\n <cqa-button variant=\"filled\" (clicked)=\"toggleFilter()\">Show Filters</cqa-button>\n </div>\n </ng-template>\n </app-dynamic-table>\n </ng-container>\n\n <ng-template #storyEmptyTpl>\n <div class=\"cqa-p-6 cqa-flex cqa-flex-col cqa-items-center cqa-justify-center\">\n <cqa-empty-state\n *ngIf=\"effectiveIsEmptyState\"\n [title]=\"effectiveEmptyStateConfig.title\"\n [description]=\"effectiveEmptyStateConfig.description\"\n [imageUrl]=\"effectiveEmptyStateConfig.imageUrl\"\n [actions]=\"effectiveEmptyStateConfig.actions\"\n (actionClick)=\"onEmptyAction($event)\"\n >\n </cqa-empty-state>\n </div>\n </ng-template>\n </div>\n\n <cqa-pagination\n [totalElements]=\"serverSidePagination ? totalElements : filteredRows.length\"\n [pageIndex]=\"pageIndex\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [pageSizeMenuDirection]=\"pageSizeMenuDirection\"\n [pageItemCount]=\"pagedRows.length\"\n (paginate)=\"onPaginate($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\"\n >\n </cqa-pagination>\n </div>\n </div>\n\n <!-- Reused bulk action toolbar -->\n <div *ngIf=\"anyRowSelected && modularConfig.allowBulkSelection && !isReordering\" class=\"cqa-absolute cqa-bottom-[18.75px] cqa-left-[50%] cqa-translate-x-[-50%] cqa-w-full lg:cqa-max-w-[68%] cqa-sm:max-w-[75%] cqa-max-w-[90%] cqa-z-[1]\">\n <cqa-table-action-toolbar\n [selectedItems]=\"selectedItems && selectedItems.length > 0 ? selectedItems : currentSelectedItems\"\n [actions]=\"effectiveBulkActions\"\n [showSelectAll]=\"showSelectAllInToolbar\"\n [allSelected]=\"allSelectedInToolbar\"\n [showDismiss]=\"showDismissInToolbar\"\n (actionClick)=\"onBulkAction($event)\"\n (selectAllChange)=\"onBulkSelectAll($event)\"\n (dismiss)=\"onBulkDismiss()\"\n ></cqa-table-action-toolbar>\n </div>\n\n </div>\n</div>\n\n", styles: [] }]
11208
+ args: [{ selector: 'cqa-modular-table-template', host: { class: 'cqa-ui-root' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Reusable folder-icon chip. Render via <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>. -->\n<ng-template #folderIconChip let-color=\"color\">\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-10 cqa-h-10 cqa-rounded-lg cqa-bg-[#F0F0F1] cqa-text-[#6D6D74] cqa-flex-shrink-0 group-hover:cqa-bg-indigo-50 group-hover:cqa-text-[#3F43EE]\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" [attr.stroke]=\"color || 'currentColor'\" stroke-width=\"0.916667\"/>\n </svg>\n </span>\n</ng-template>\n\n<!-- Reusable indeterminate loading spinner. Render via\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 20 }\"></ng-container>. -->\n<ng-template #loadingSpinner let-size=\"size\">\n <svg [attr.width]=\"size || 20\" [attr.height]=\"size || 20\" viewBox=\"0 0 50 50\" aria-label=\"loading\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"25\" cy=\"25\" r=\"20\" stroke=\"#E5E7EB\" stroke-width=\"6\" fill=\"none\"></circle>\n <path d=\"M45 25a20 20 0 0 0-20-20\" stroke=\"#4F46E5\" stroke-width=\"6\" fill=\"none\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 25 25\" to=\"360 25 25\" dur=\"0.8s\" repeatCount=\"indefinite\"></animateTransform>\n </path>\n </svg>\n</ng-template>\n\n<!-- Reusable \"+\" chip used by the \"New folder\" tile. -->\n<ng-template #addIconChip>\n <span class=\"cqa-inline-flex cqa-items-center cqa-justify-center cqa-w-10 cqa-h-10 cqa-rounded-lg cqa-bg-[#F0F0F1] cqa-text-[#6D6D74] cqa-flex-shrink-0 group-hover:cqa-bg-indigo-50 group-hover:cqa-text-[#3F43EE]\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M8 3V13M3 8H13\" stroke=\"currentColor\" stroke-width=\"1.275\" stroke-linecap=\"round\"/>\n </svg>\n </span>\n</ng-template>\n\n<div class=\"cqa-ui-root\">\n <div class=\"cqa-w-full cqa-flex cqa-flex-col cqa-relative\">\n <div [class]=\"!showSearchBar ? 'cqa-justify-end' : 'cqa-justify-between'\" class=\"cqa-w-full cqa-flex cqa-items-center cqa-gap-3 cqa-flex-wrap cqa-mb-3\">\n <cqa-search-bar\n *ngIf=\"showSearchBar\"\n [placeholder]=\"searchPlaceholder\"\n [value]=\"searchValue\"\n [showClear]=\"showClear\"\n (valueChange)=\"valueChange($event)\"\n (search)=\"search($event)\"\n (cleared)=\"cleared()\"\n ></cqa-search-bar>\n <div class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-flex-wrap\">\n <cqa-button\n *ngIf=\"showExportButton\"\n variant=\"grey-solid\"\n icon=\"open_in_new\"\n [text]=\"isExporting ? 'Exporting...' : 'Export'\"\n [disabled]=\"isExporting\"\n (clicked)=\"exportCodeClick()\"\n >\n <span>{{ isExporting ? 'Exporting...' : 'Export' }}</span>\n </cqa-button>\n \n <!-- Export Code Modal -->\n <cqa-export-code-modal\n *ngIf=\"showExportButton\"\n [isOpen]=\"isExportModalOpen\"\n [cases]=\"selectedCasesForExport\"\n [disabled]=\"false\"\n (closeModal)=\"closeExportModal()\"\n (export)=\"onExportModalExport($event)\">\n </cqa-export-code-modal>\n <ng-container *ngFor=\"let dropdownTemplate of otherDropDownButtons; trackBy: trackByDropdownTemplateRef\">\n <ng-container *ngTemplateOutlet=\"dropdownTemplate\"></ng-container>\n </ng-container>\n\n <ng-container *ngFor=\"let selectDropdownTemplate of otherSelectDropDownButtons; trackBy: trackBySelectDropdownTemplateRef\">\n <ng-container *ngTemplateOutlet=\"selectDropdownTemplate\"></ng-container>\n </ng-container>\n \n <cqa-button\n *ngIf=\"showFilterButton\"\n variant=\"grey-solid\"\n icon=\"add\"\n [disabled]=\"isReordering\"\n (clicked)=\"toggleFilter()\"\n >\n <span class=\"cqa-flex cqa-items-center cqa-gap-1\">\n Filter \n <div [class]=\"arrowClasses\">\n <svg\n class=\"cqa-w-2 cqa-h-1 cqa-absolute cqa-left-[4px] cqa-top-[6px]\"\n viewBox=\"0 0 8 4\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M0 0L4 4L8 0\"\n stroke=\"#0B0B0C\"\n stroke-width=\"1.33\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </div>\n </span>\n </cqa-button>\n <cqa-column-visibility\n *ngIf=\"showSettingsButton\"\n [columns]=\"visibilityColumns\"\n [columnVisibility]=\"columnVisibility\"\n [selectedAutoRefreshInterval]=\"selectedAutoRefreshInterval\"\n (columnVisibilityChange)=\"onColumnVisibilityChange($event)\"\n (autoRefreshChange)=\"onAutoRefreshChange($event)\"\n ></cqa-column-visibility>\n <cqa-button\n *ngIf=\"showAutoRefreshButton\"\n variant=\"grey-solid\"\n icon=\"refresh\"\n (clicked)=\"handleRefreshClick()\"\n [tooltip]=\"'Refresh'\"\n tooltipPosition=\"below\"\n [disabled]=\"isReordering\"\n ></cqa-button>\n <ng-container *ngFor=\"let buttonTemplate of otherButtons; trackBy: trackByTemplateRef\">\n <ng-container *ngTemplateOutlet=\"buttonTemplate\"></ng-container>\n </ng-container>\n <cqa-segment-control\n *ngIf=\"showViewModeToggle\"\n size=\"lg\"\n [segments]=\"viewModeSegments\"\n [value]=\"viewMode\"\n (valueChange)=\"onViewModeChange($event)\"\n ></cqa-segment-control>\n <cqa-button\n *ngIf=\"showReorderButton && !isReordering\"\n variant=\"outlined\"\n icon=\"drag_indicator\"\n [text]=\"reorderLabels.reorderButton\"\n [disabled]=\"!pagedRows || pagedRows.length === 0 || isTableLoading || isTableDataLoading\"\n (clicked)=\"startReorder()\"\n ></cqa-button>\n <ng-container *ngIf=\"showReorderButton && isReordering\">\n <cqa-button\n variant=\"outlined\"\n [text]=\"reorderLabels.cancelButton\"\n [disabled]=\"reorderSaving\"\n (clicked)=\"cancelReorder()\"\n ></cqa-button>\n <cqa-button\n variant=\"filled\"\n [icon]=\"reorderSaving ? 'hourglass_empty' : ''\"\n [text]=\"reorderSaving ? reorderLabels.savingButton : reorderLabels.doneButton\"\n [disabled]=\"reorderSaving\"\n (clicked)=\"saveReorder()\"\n ></cqa-button>\n </ng-container>\n </div>\n </div>\n\n <!-- Reorder mode banner -->\n <div *ngIf=\"isReordering\" class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-3 cqa-px-3 cqa-py-2 cqa-rounded-md cqa-bg-[#FFFBEB] cqa-border cqa-border-[#FDE68A]\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <circle cx=\"3\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"3\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"3\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"8\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"3\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"8\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n <circle cx=\"13\" cy=\"13\" r=\"1.5\" fill=\"#92400E\"></circle>\n </svg>\n <span class=\"cqa-text-sm cqa-font-semibold cqa-text-[#92400E]\">{{ reorderLabels.bannerTitle }}</span>\n <span class=\"cqa-text-sm cqa-text-[#92400E]\">{{ reorderLabels.bannerDescription }}</span>\n </div>\n\n <cqa-selected-filters\n [filterApplied]=\"filterApplied\"\n [chips]=\"chips\"\n (removeChip)=\"onRemoveChip($event)\"\n (clearAll)=\"onClearAllChips()\"\n (onClearAll)=\"onClearAll.emit()\"\n >\n </cqa-selected-filters>\n\n <cqa-dynamic-filter\n *ngIf=\"showFilterPanel\"\n [config]=\"filterConfig\"\n [model]=\"filterModel\"\n [showFilterPanel]=\"showFilterPanel\"\n (filtersChanged)=\"onFiltersChanged($event)\"\n (filtersApplied)=\"onFiltersApplied($event)\"\n (onApplyFilterClick)=\"onApplyFilterClick.emit($event)\"\n (onResetFilterClick)=\"handleResetFilterClick()\"\n >\n </cqa-dynamic-filter>\n\n <div class=\"cqa-flex cqa-items-stretch cqa-gap-2 cqa-min-h-0 \" style=\"border-bottom: 1px solid rgb(226, 226, 227);\">\n <!-- Sidebar (only shown in modular view) -->\n <cqa-folder-sidebar\n *ngIf=\"isModularView && modularConfig.showSidebar\"\n [folders]=\"folders\"\n [selectedFolderId]=\"selectedFolderId\"\n [expandedFolderIds]=\"expandedFolderIds\"\n [unorganizedCount]=\"unorganizedCount\"\n [allowCreate]=\"modularConfig.allowCreateFolder\"\n [allowRename]=\"modularConfig.allowRenameFolder\"\n [allowDelete]=\"modularConfig.allowDeleteFolder\"\n [allowDrop]=\"modularConfig.allowTestDragDrop\"\n [showCounts]=\"modularConfig.showCounts\"\n [collapsed]=\"sidebarCollapsed\"\n [labels]=\"modularLabels\"\n [serverSideSearch]=\"serverSideSearch\"\n [rootTotal]=\"rootTotal\"\n [folderSearchLoading]=\"folderSearchLoading\"\n [rootFoldersLoading]=\"rootFoldersLoading\"\n [savingFolderIds]=\"savingFolderIds\"\n [searchValue]=\"folderSearchValue\"\n (folderSelected)=\"onFolderSelected($event)\"\n (folderExpansionToggled)=\"onFolderExpansionToggled($event)\"\n (folderChildrenRequested)=\"folderChildrenRequested.emit($event)\"\n (folderLoadMoreRequested)=\"folderLoadMoreRequested.emit($event)\"\n (rootLoadMoreRequested)=\"rootLoadMoreRequested.emit()\"\n (searchChange)=\"folderSearchChange.emit($event)\"\n (folderCreated)=\"onFolderCreated($event)\"\n (folderCreateRequested)=\"onFolderCreateRequested($event)\"\n (folderRenamed)=\"onFolderRenamed($event)\"\n (folderDeleted)=\"onFolderDeleted($event)\"\n (folderMoveRequested)=\"onFolderMoveRequested($event)\"\n (folderDuplicateRequested)=\"onFolderDuplicateRequested($event)\"\n (testsDropped)=\"onTestsDropped($event)\"\n (folderDropped)=\"onFolderDropped($event)\"\n (collapsedChange)=\"onSidebarCollapsedChange($event)\"\n ></cqa-folder-sidebar>\n\n <!-- Right pane -->\n <div\n class=\"cqa-flex-1 cqa-flex cqa-flex-col cqa-min-w-0\"\n [style.border-top]=\"isModularView ? '1px solid #E2E2E3' : null\"\n [style.border-left]=\"isModularView ? '1px solid #E2E2E3' : null\"\n [style.border-right]=\"isModularView ? '1px solid #E2E2E3' : null\"\n >\n <!-- Breadcrumb (modular view, folder drilled-in) -->\n <nav\n *ngIf=\"isModularView && modularConfig.showBreadcrumb && !isRootView\"\n aria-label=\"Folder path\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-text-sm cqa-px-3 cqa-py-2 cqa-rounded-none cqa-min-w-0\"\n style=\"color: #6D6D74; border-bottom: 1px solid #E2E2E3;\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-1 cqa-flex-1 cqa-min-w-0 cqa-overflow-x-auto cqa-scrollbar-thin cqa-whitespace-nowrap\">\n <button type=\"button\" class=\"hover:cqa-text-[#3F43EE] cqa-inline-flex cqa-items-center cqa-gap-1 cqa-flex-shrink-0\" (click)=\"onFolderSelected(null)\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" stroke=\"currentColor\" stroke-width=\"0.916667\"/>\n </svg>\n <span>{{ modularLabels.allFolders }}</span>\n </button>\n <ng-container *ngFor=\"let crumb of breadcrumbTrail; let last = last\">\n <mat-icon class=\"cqa-align-middle cqa-flex-shrink-0\" style=\"font-size:16px;width:16px;height:16px;color:#6D6D74;\">chevron_right</mat-icon>\n <button\n type=\"button\"\n class=\"cqa-flex-shrink-0\"\n [ngClass]=\"last\n ? 'cqa-text-[#161617] cqa-font-semibold'\n : 'cqa-text-[#6D6D74] hover:cqa-text-[#3F43EE]'\"\n [disabled]=\"last\"\n (click)=\"!last && onFolderSelected(crumb.id)\"\n >{{ crumb.name }}</button>\n </ng-container>\n </div>\n <cqa-button\n *ngIf=\"modularConfig.showSubfolderSection && subfolderTiles.length\"\n class=\"cqa-flex-shrink-0\"\n variant=\"text\"\n btnSize=\"sm\"\n customClass=\"!cqa-text-[13px] !cqa-leading-[18px]\"\n [icon]=\"subfolderSectionExpanded ? 'expand_less' : 'expand_more'\"\n [text]=\"(subfolderSectionExpanded ? 'Hide' : 'Show') + ' subfolders'\"\n [attr.aria-expanded]=\"subfolderSectionExpanded\"\n (clicked)=\"toggleSubfolderSection()\"\n ></cqa-button>\n <cqa-button\n *ngIf=\"modularConfig.allowCreateFolder\"\n class=\"cqa-flex-shrink-0\"\n variant=\"text\"\n btnSize=\"sm\"\n customClass=\"!cqa-text-[13px] !cqa-leading-[18px]\"\n icon=\"add\"\n [text]=\"modularLabels.newFolder\"\n (clicked)=\"onFolderCreateRequested({ parentId: selectedFolderId })\"\n ></cqa-button>\n </nav>\n\n <!-- Folder grid: modular + root view -->\n <section\n *ngIf=\"isModularView && isRootView && modularConfig.showFolderGrid && rootFolderTiles.length\"\n class=\"cqa-px-5 cqa-pt-2\"\n style=\"border-bottom: 1px solid #F0F0F1;\"\n >\n <button\n type=\"button\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-2 cqa-w-full cqa-text-left\"\n [attr.aria-expanded]=\"organizedSectionExpanded\"\n (click)=\"toggleOrganizedSection()\"\n >\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9583 10.4585C12.9583 10.79 12.8266 11.108 12.5921 11.3424C12.3577 11.5768 12.0398 11.7085 11.7083 11.7085H1.70825C1.37673 11.7085 1.05879 11.5768 0.824368 11.3424C0.589948 11.108 0.458252 10.79 0.458252 10.4585V1.7085C0.458252 1.37698 0.589948 1.05903 0.824368 0.824613C1.05879 0.590192 1.37673 0.458496 1.70825 0.458496H4.83325L6.08325 2.3335H11.7083C12.0398 2.3335 12.3577 2.46519 12.5921 2.69961C12.8266 2.93403 12.9583 3.25198 12.9583 3.5835V10.4585Z\" stroke=\"#6D6D74\" stroke-width=\"0.916667\"/>\n </svg>\n <h3 class=\"cqa-text-sm cqa-font-semibold cqa-text-[#6D6D74]\">{{ modularLabels.organized }}</h3>\n <span class=\"cqa-text-xs cqa-text-[#6D6D74]\">\n {{ rootFolderTiles.length === 1 ? modularLabels.foldersCountSingular : modularLabels.foldersCountPlural.replace('{n}', '' + rootFolderTiles.length) }}\n </span>\n <mat-icon class=\"cqa-text-[#6D6D74] cqa-ml-auto\" style=\"font-size:18px;width:18px;height:18px\">\n {{ organizedSectionExpanded ? 'expand_less' : 'expand_more' }}\n </mat-icon>\n </button>\n <div\n class=\"cqa-grid\"\n style=\"transition: grid-template-rows 300ms cubic-bezier(0.4, 0, 0.2, 1);\"\n [style.grid-template-rows]=\"organizedSectionExpanded ? '1fr' : '0fr'\"\n >\n <div class=\"cqa-min-h-0\" style=\"overflow: hidden;\">\n <div class=\"cqa-flex cqa-flex-nowrap cqa-gap-3 cqa-overflow-x-auto cqa-scrollbar-thin cqa-pb-1\"\n (scroll)=\"onRootGridScroll($event)\">\n <button\n *ngFor=\"let f of rootFolderTiles\"\n type=\"button\"\n [cqaFolderDrop]=\"f.id\"\n [dropEnabled]=\"modularConfig.allowTestDragDrop\"\n (testsDropped)=\"onTestsDropped($event)\"\n (click)=\"onFolderSelected(f.id)\"\n class=\"cqa-group cqa-flex-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-justify-between cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-solid cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-white hover:cqa-border-[#3F43EE] hover:cqa-shadow-sm cqa-transition-colors\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-3 cqa-min-w-0\">\n <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ f.name }}</span>\n <span *ngIf=\"modularConfig.showCounts\" class=\"cqa-text-sm cqa-text-neutral-500 cqa-truncate\">\n {{ (f.count ?? 0) === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + (f.count ?? 0)) }}<ng-container *ngIf=\"f.children?.length\"> \u00B7 {{ f.children!.length === 1 ? modularLabels.oneSubfolder : modularLabels.subfoldersCount.replace('{n}', '' + f.children!.length) }}</ng-container>\n </span>\n </div>\n </div>\n <mat-icon class=\"cqa-text-neutral-400 cqa-flex-shrink-0\" style=\"font-size:20px;width:20px;height:20px\">chevron_right</mat-icon>\n </button>\n\n <!-- \"+ New folder\" tile -->\n <button\n *ngIf=\"modularConfig.allowCreateFolder\"\n type=\"button\"\n (click)=\"onFolderCreateRequested({ parentId: null })\"\n class=\"cqa-group cqa-flex-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-dashed cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-[#FAFAFA] hover:cqa-border-[#3F43EE] hover:cqa-bg-white cqa-transition-colors\"\n >\n <ng-container *ngTemplateOutlet=\"addIconChip\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ modularLabels.newFolder }}</span>\n <span class=\"cqa-text-sm cqa-text-neutral-400\">{{ modularLabels.testsCountPlural.replace('{n}', '0') }}</span>\n </div>\n </button>\n\n <!-- Pagination spinner anchored at the right edge of the Organized\n row. Only renders during an in-flight root fetch (initial OR\n scroll-driven \"load more\"), and is filtered to suppress when\n the section is empty so we don't show a spinner inside an\n otherwise-blank row. -->\n <div\n *ngIf=\"rootFoldersLoading && rootFolderTiles.length > 0\"\n class=\"cqa-flex-shrink-0 cqa-flex cqa-items-center cqa-justify-center cqa-w-[80px]\"\n aria-live=\"polite\"\n [attr.aria-label]=\"'Loading more folders'\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 20 }\"></ng-container>\n </div>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Subfolder section: modular + folder view. Shown whenever drilled in\n (even if no subfolders, so the \"+ New folder\" tile remains\n reachable, and so we can render a loading state during the\n drill-in fetch before children arrive). -->\n <section\n *ngIf=\"isModularView && !isRootView && modularConfig.showSubfolderSection && (subfolderTiles.length || currentFolderNode?.childrenLoading)\"\n class=\"cqa-grid\"\n style=\"transition: grid-template-rows 300ms cubic-bezier(0.4, 0, 0.2, 1);\"\n [style.grid-template-rows]=\"subfolderSectionExpanded ? '1fr' : '0fr'\"\n >\n <div class=\"cqa-min-h-0\" style=\"overflow: hidden;\">\n <div class=\"cqa-px-3 cqa-py-2 cqa-flex cqa-flex-nowrap cqa-gap-3 cqa-overflow-x-auto cqa-scrollbar-thin\"\n (scroll)=\"onSubfolderGridScroll($event)\">\n <button\n *ngFor=\"let f of subfolderTiles\"\n type=\"button\"\n [cqaFolderDrop]=\"f.id\"\n [dropEnabled]=\"modularConfig.allowTestDragDrop\"\n (testsDropped)=\"onTestsDropped($event)\"\n (click)=\"onFolderSelected(f.id)\"\n class=\"cqa-group cqa-flex-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-justify-between cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-solid cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-white hover:cqa-border-[#3F43EE] hover:cqa-shadow-sm cqa-transition-colors\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-3 cqa-min-w-0\">\n <ng-container *ngTemplateOutlet=\"folderIconChip; context: { color: f.color }\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ f.name }}</span>\n <span *ngIf=\"modularConfig.showCounts\" class=\"cqa-text-sm cqa-text-neutral-500 cqa-truncate\">\n {{ (f.count ?? 0) === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + (f.count ?? 0)) }}\n </span>\n </div>\n </div>\n <mat-icon class=\"cqa-text-neutral-400 cqa-flex-shrink-0\" style=\"font-size:20px;width:20px;height:20px\">chevron_right</mat-icon>\n </button>\n\n <!-- \"+ New folder\" tile -->\n <button\n *ngIf=\"modularConfig.allowCreateFolder\"\n type=\"button\"\n (click)=\"onFolderCreateRequested({ parentId: selectedFolderId })\"\n class=\"cqa-group cqa-flex-shrink-0 cqa-w-[240px] cqa-flex cqa-items-center cqa-gap-3 cqa-text-left cqa-p-3 cqa-rounded-[10px] cqa-border-dashed cqa-border-[0.5px] cqa-border-[#E2E2E3] cqa-bg-[#FAFAFA] hover:cqa-border-[#3F43EE] hover:cqa-bg-white cqa-transition-colors\"\n >\n <ng-container *ngTemplateOutlet=\"addIconChip\"></ng-container>\n <div class=\"cqa-flex cqa-flex-col cqa-min-w-0\">\n <span class=\"cqa-text-[15px] cqa-text-[#161617] cqa-truncate cqa-leading-tight group-hover:cqa-text-[#3F43EE]\">{{ modularLabels.newFolder }}</span>\n <span class=\"cqa-text-sm cqa-text-neutral-400\">{{ modularLabels.testsCountPlural.replace('{n}', '0') }}</span>\n </div>\n </button>\n\n <!-- Subfolder fetch indicator. Two cases:\n - Drill-in fetch: no subfolderTiles yet, render a centered\n row spinner so the section isn't empty during the fetch.\n - Pagination fetch: tiles exist, anchor a small spinner at\n the right edge so the user knows the next page is loading. -->\n <div\n *ngIf=\"currentFolderNode?.childrenLoading\"\n class=\"cqa-flex-shrink-0 cqa-flex cqa-items-center cqa-justify-center cqa-w-[80px]\"\n aria-live=\"polite\"\n [attr.aria-label]=\"'Loading subfolders'\"\n >\n <ng-container *ngTemplateOutlet=\"loadingSpinner; context: { size: 20 }\"></ng-container>\n </div>\n </div>\n </div>\n </section>\n\n <div\n *ngIf=\"isModularView && !isRootView && modularConfig.showUnorganizedSection\"\n class=\"cqa-flex cqa-items-center cqa-justify-between cqa-px-4 cqa-py-3 cqa-border cqa-border-indigo-100\"\n style=\"background-color: #D8D9FC;\"\n >\n <div class=\"cqa-flex cqa-items-center cqa-gap-2\">\n <svg width=\"14\" height=\"13\" viewBox=\"0 0 14 13\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M12.9585 10.4585C12.9585 10.79 12.8268 11.108 12.5924 11.3424C12.358 11.5768 12.04 11.7085 11.7085 11.7085H1.7085C1.37698 11.7085 1.05903 11.5768 0.824613 11.3424C0.590192 11.108 0.458496 10.79 0.458496 10.4585V1.7085C0.458496 1.37698 0.590192 1.05903 0.824613 0.824613C1.05903 0.590192 1.37698 0.458496 1.7085 0.458496H4.8335L6.0835 2.3335H11.7085C12.04 2.3335 12.358 2.46519 12.5924 2.69961C12.8268 2.93403 12.9585 3.25198 12.9585 3.5835V10.4585Z\" stroke=\"#3F43EE\" stroke-width=\"0.916667\"/>\n </svg>\n <span class=\"cqa-text-sm cqa-font-semibold\" style=\"color: #3F43EE;\">\n {{ currentFolderNode?.name }} ({{ currentFolderDirectCount }})\n </span>\n </div>\n <cqa-button\n variant=\"outlined\"\n btnSize=\"sm\"\n customClass=\"!cqa-text-[13px]\"\n inlineStyles=\"border-color: #3F43EE; color: #3F43EE;\"\n [text]=\"modularLabels.clearFilter\"\n (clicked)=\"onFolderSelected(null)\"\n ></cqa-button>\n </div>\n\n <!-- Unorganized section header at root view -->\n <div\n *ngIf=\"isModularView && isRootView && modularConfig.showUnorganizedSection\"\n class=\"cqa-flex cqa-items-center cqa-gap-2 cqa-mb-2 cqa-mt-2\" style=\"margin-left: 20px;\"\n >\n <mat-icon class=\"cqa-text-[#6D6D74]\" style=\"font-size:16px;width:16px;height:16px\">inbox</mat-icon>\n <h3 class=\"cqa-text-sm cqa-font-semibold cqa-text-[#6D6D74]\">{{ modularLabels.unorganized }}</h3>\n <span class=\"cqa-text-xs cqa-text-[#6D6D74]\">\n {{ unorganizedCount === 1 ? modularLabels.testsCountSingular : modularLabels.testsCountPlural.replace('{n}', '' + unorganizedCount) }}\n </span>\n </div>\n\n <div class=\"cqa-overflow-hidden cqa-border-t cqa-border-l cqa-border-r cqa-border-grey-200 cqa-relative cqa-overflow-x-auto\"\n [class.cqa-modular-table-flush]=\"isModularView\"\n [attr.style]=\"(!isModularView ? 'border-radius: 7px;' : '') + ((pagedRows && pagedRows.length > 0) ? '' : ' border-bottom: none !important;')\">\n <ng-container *ngIf=\"(isTableLoading || isTableDataLoading) || (!effectiveIsEmptyState && pagedRows && pagedRows.length > 0); else storyEmptyTpl\">\n <app-dynamic-table\n [style.border-bottom]=\"pagedRows && pagedRows.length > 0 ? '1px solid rgb(226, 226, 227)' : null\"\n [columns]=\"computedColumns\"\n [data]=\"pagedRows\"\n [rowSelectable]=\"rowSelectable\"\n [enableLocalSort]=\"enableLocalSort && !isReordering\"\n [isTableLoading]=\"isTableLoading\"\n [isTableDataLoading]=\"isTableDataLoading\"\n [cellJsonPathGetter]=\"cellJsonPathGetter\"\n [onJsonPathCopiedHandler]=\"onJsonPathCopiedHandler\"\n [enableRowReorder]=\"isReordering\"\n (rowReorder)=\"onRowReorder($event)\"\n (sortChange)=\"sortChange.emit($event)\">\n <ng-template #emptyTableTpl>\n <div class=\"cqa-flex cqa-flex-col cqa-items-center cqa-justify-center cqa-py-8\">\n <img src=\"/assets/illustrations/empty-state.svg\" alt=\"No data\" class=\"cqa-w-32 cqa-h-32 cqa-mb-4\" />\n <h3 class=\"cqa-text-lg cqa-font-semibold cqa-mb-2\">No test cases</h3>\n <p class=\"cqa-text-sm cqa-text-neutral-500 cqa-mb-4\">Try adjusting filters or create a new test case.</p>\n <cqa-button variant=\"filled\" (clicked)=\"toggleFilter()\">Show Filters</cqa-button>\n </div>\n </ng-template>\n </app-dynamic-table>\n </ng-container>\n\n <ng-template #storyEmptyTpl>\n <div class=\"cqa-p-6 cqa-flex cqa-flex-col cqa-items-center cqa-justify-center\">\n <cqa-empty-state\n *ngIf=\"effectiveIsEmptyState\"\n [title]=\"effectiveEmptyStateConfig.title\"\n [description]=\"effectiveEmptyStateConfig.description\"\n [imageUrl]=\"effectiveEmptyStateConfig.imageUrl\"\n [actions]=\"effectiveEmptyStateConfig.actions\"\n (actionClick)=\"onEmptyAction($event)\"\n >\n </cqa-empty-state>\n </div>\n </ng-template>\n </div>\n\n <cqa-pagination\n [totalElements]=\"serverSidePagination ? totalElements : filteredRows.length\"\n [pageIndex]=\"pageIndex\"\n [pageSize]=\"pageSize\"\n [pageSizeOptions]=\"pageSizeOptions\"\n [pageSizeMenuDirection]=\"pageSizeMenuDirection\"\n [pageItemCount]=\"pagedRows.length\"\n (paginate)=\"onPaginate($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\"\n >\n </cqa-pagination>\n </div>\n </div>\n\n <!-- Reused bulk action toolbar -->\n <div *ngIf=\"anyRowSelected && modularConfig.allowBulkSelection && !isReordering\" class=\"cqa-absolute cqa-bottom-[18.75px] cqa-left-[50%] cqa-translate-x-[-50%] cqa-w-full lg:cqa-max-w-[68%] cqa-sm:max-w-[75%] cqa-max-w-[90%] cqa-z-[1]\">\n <cqa-table-action-toolbar\n [selectedItems]=\"selectedItems && selectedItems.length > 0 ? selectedItems : currentSelectedItems\"\n [actions]=\"effectiveBulkActions\"\n [showSelectAll]=\"showSelectAllInToolbar\"\n [allSelected]=\"allSelectedInToolbar\"\n [showDismiss]=\"showDismissInToolbar\"\n (actionClick)=\"onBulkAction($event)\"\n (selectAllChange)=\"onBulkSelectAll($event)\"\n (dismiss)=\"onBulkDismiss()\"\n ></cqa-table-action-toolbar>\n </div>\n\n </div>\n</div>\n\n", styles: [] }]
11090
11209
  }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: DialogService }]; }, propDecorators: { searchPlaceholder: [{
11091
11210
  type: Input
11092
11211
  }], searchValue: [{
@@ -11217,6 +11336,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
11217
11336
  type: Input
11218
11337
  }], sidebarCollapsed: [{
11219
11338
  type: Input
11339
+ }], subfolderSectionExpanded: [{
11340
+ type: Input
11341
+ }], organizedSectionExpanded: [{
11342
+ type: Input
11220
11343
  }], serverSideSearch: [{
11221
11344
  type: Input
11222
11345
  }], rootTotal: [{
@@ -11227,6 +11350,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
11227
11350
  type: Input
11228
11351
  }], rootFoldersLoading: [{
11229
11352
  type: Input
11353
+ }], savingFolderIds: [{
11354
+ type: Input
11230
11355
  }], selectedFolderNode: [{
11231
11356
  type: Input
11232
11357
  }], selectedFolderTrail: [{
@@ -11265,6 +11390,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
11265
11390
  type: Output
11266
11391
  }], sidebarCollapsedChange: [{
11267
11392
  type: Output
11393
+ }], subfolderSectionExpandedChange: [{
11394
+ type: Output
11395
+ }], organizedSectionExpandedChange: [{
11396
+ type: Output
11268
11397
  }], showReorderButton: [{
11269
11398
  type: Input
11270
11399
  }], reorderSaving: [{