@ascentgl/ads-ui 21.57.0 → 21.59.0

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.
@@ -7135,10 +7135,16 @@ class AdsColumnSortFilterMenuComponent {
7135
7135
  return;
7136
7136
  }
7137
7137
  this.initializedConfigField = this.config.field;
7138
+ // Determine if we should default to all selected or use the provided selectedFilterValues
7139
+ // If selectedFilterValues equals all filterOptions, it means "all selected" (no filter active)
7140
+ // If selectedFilterValues is empty [], it means "none selected" (show nothing)
7141
+ // If selectedFilterValues has some values, use those
7142
+ const allOptionsSelected = this.selectedFilterValues.length === this.config.filterOptions.length &&
7143
+ this.config.filterOptions.every(opt => this.selectedFilterValues.includes(opt));
7138
7144
  const options = this.config.filterOptions.map((value) => {
7139
- // If selectedFilterValues is empty, default to selected (initial state before parent sets values)
7140
- // Otherwise, check if value is in selectedFilterValues
7141
- const isSelected = this.selectedFilterValues.length === 0 || this.selectedFilterValues.includes(value);
7145
+ // If all options match selectedFilterValues, treat as all selected
7146
+ // Otherwise, check if this specific value is in selectedFilterValues
7147
+ const isSelected = allOptionsSelected || this.selectedFilterValues.includes(value);
7142
7148
  const control = new FormControl(isSelected, { nonNullable: true });
7143
7149
  // Subscribe to control value changes
7144
7150
  const subscription = control.valueChanges.subscribe(() => {
@@ -7266,11 +7272,11 @@ class AdsColumnSortFilterMenuComponent {
7266
7272
  return text.length > this.MAX_LABEL_LENGTH;
7267
7273
  }
7268
7274
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AdsColumnSortFilterMenuComponent, deps: [{ token: i1.AdsIconRegistry }], target: i0.ɵɵFactoryTarget.Component }); }
7269
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: AdsColumnSortFilterMenuComponent, isStandalone: false, selector: "ads-column-sort-filter-menu", inputs: { config: "config", currentSortDirection: "currentSortDirection", selectedFilterValues: "selectedFilterValues" }, outputs: { sortChanged: "sortChanged", filterChanged: "filterChanged", hideColumn: "hideColumn" }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"column-sort-filter-menu\"\n (click)=\"$event.stopPropagation()\"\n [class.full-height]=\"hasFilterOptions && hasSortOptions\"\n>\n <!-- Sort Options -->\n @if (hasSortOptions) {\n <div class=\"sort-section\">\n @for (sortOption of sortOptions; track sortOption.direction) {\n <div\n class=\"sort-option\"\n [class.active]=\"isSortActive(sortOption.direction)\"\n (click)=\"onSortOptionClick(sortOption.direction)\"\n >\n <ads-icon\n [name]=\"sortOption.direction === 'asc' ? 'sort_down_to_up' : 'sort_up_to_down'\"\n [theme]=\"isSortActive(sortOption.direction) ? 'secondary' : 'iconPrimary'\"\n size=\"xxs_16\"\n class=\"sort-icon\"\n />\n <span class=\"sort-label\">{{ sortOption.label }}</span>\n @if (isSortActive(sortOption.direction)) {\n <ads-icon\n name=\"check_circle_filled\"\n theme=\"secondary\"\n size=\"xxs_16\"\n />\n }\n </div>\n }\n </div>\n }\n\n @if (hasFilterOptions && hasSortOptions) {\n <ads-divider />\n }\n\n <!-- Filter Section -->\n @if (hasFilterOptions) {\n <div class=\"filter-section\">\n <ads-search-input\n [control]=\"searchControl\"\n placeholder=\"Filter Search\"\n [showFooter]=\"false\"\n />\n\n @if (noResultsFound()) {\n <p class=\"no-results\">\n Can't find \"{{ searchValue() }}.\" Try searching something else.\n </p>\n } @else {\n <div class=\"filter-options\">\n <!-- Select All - only show when not searching -->\n @if (!searchValue()) {\n <ads-checkbox\n [control]=\"selectAllControl\"\n label=\"Select All\"\n [showFooter]=\"false\"\n />\n }\n\n <!-- Filter Options List -->\n @for (option of filteredOptions(); track option.value) {\n <div class=\"filter-option-wrapper\" [matTooltip]=\"option.label\" [matTooltipDisabled]=\"!isTextTruncated(option.label)\">\n <ads-checkbox\n [control]=\"option.control\"\n [label]=\"option.label\"\n [showFooter]=\"false\"\n />\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Hide Column -->\n <div class=\"hide-column-section\" (click)=\"onHideColumnClick()\">\n <ads-icon name=\"visibility_eye_none\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"xxs_16\" />\n <span>Hide Column</span>\n </div>\n</div>\n\n", styles: [".column-sort-filter-menu{width:234px;max-height:358px;background-color:var(--color-white);display:flex;flex-direction:column}.column-sort-filter-menu.full-height{height:358px}.column-sort-filter-menu .sort-section{padding:0;flex-shrink:0}.column-sort-filter-menu .sort-section .sort-option{display:flex;align-items:center;gap:8px;padding:12px;cursor:pointer;transition:background-color .2s ease}.column-sort-filter-menu .sort-section .sort-option .sort-label{font-size:16px;line-height:21px;color:var(--color-dark)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active){background-color:var(--color-secondary-hover)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active) .sort-label{color:var(--color-white)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active) .sort-icon ::ng-deep svg{fill:var(--color-white)!important}.column-sort-filter-menu .sort-section .sort-option.active{background-color:var(--color-secondary-10)}.column-sort-filter-menu .filter-section{padding:0 12px;flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.column-sort-filter-menu .filter-section ads-search-input{display:block;padding:12px 0;flex-shrink:0}.column-sort-filter-menu .filter-section .no-results{color:var(--color-error);font-size:12px;line-height:16px;padding:12px 0;font-weight:600;margin:0}.column-sort-filter-menu .filter-section .filter-options{flex:1;overflow-y:auto;overflow-x:hidden;min-height:0}.column-sort-filter-menu .filter-section .filter-options .filter-option-wrapper ads-checkbox{display:block}.column-sort-filter-menu .filter-section .filter-options .filter-option-wrapper ads-checkbox ::ng-deep .checkbox-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:160px}.column-sort-filter-menu .hide-column-section{display:flex;align-items:center;gap:8px;padding:12px;cursor:pointer;transition:background-color .2s ease;flex-shrink:0;border-top:1px solid var(--color-light)}.column-sort-filter-menu .hide-column-section:hover{background-color:var(--color-secondary-hover)}.column-sort-filter-menu .hide-column-section:hover span{color:var(--color-white)}.column-sort-filter-menu .hide-column-section:hover ads-icon ::ng-deep svg{fill:var(--color-white)!important;stroke:var(--color-white)!important}.column-sort-filter-menu .hide-column-section:active{background-color:var(--color-secondary-pressed)}.column-sort-filter-menu .hide-column-section:active span{color:var(--color-white)}.column-sort-filter-menu .hide-column-section:active ads-icon ::ng-deep svg{fill:var(--color-white)!important;stroke:var(--color-white)!important}.column-sort-filter-menu .hide-column-section span{font-size:16px;line-height:21px;color:var(--color-dark)}\n"], dependencies: [{ kind: "component", type: i1.AdsIconComponent, selector: "ads-icon", inputs: ["size", "name", "color", "theme", "stroke"] }, { kind: "component", type: AdsSearchInputComponent, selector: "ads-search-input", inputs: ["searchCallback", "isIconClickable", "searchEmptyValue", "loading"] }, { kind: "component", type: AdsCheckboxComponent, selector: "ads-checkbox", inputs: ["indeterminate", "width", "tooltip", "tooltipHref", "size"] }, { kind: "component", type: DividerComponent, selector: "ads-divider", inputs: ["margin", "color"] }, { kind: "directive", type: i13.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] }); }
7275
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: AdsColumnSortFilterMenuComponent, isStandalone: false, selector: "ads-column-sort-filter-menu", inputs: { config: "config", currentSortDirection: "currentSortDirection", selectedFilterValues: "selectedFilterValues" }, outputs: { sortChanged: "sortChanged", filterChanged: "filterChanged", hideColumn: "hideColumn" }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"column-sort-filter-menu\"\n (click)=\"$event.stopPropagation()\"\n [class.full-height]=\"hasFilterOptions && hasSortOptions\"\n>\n <!-- Sort Options -->\n @if (hasSortOptions) {\n <div class=\"sort-section\">\n @for (sortOption of sortOptions; track sortOption.direction) {\n <div\n class=\"sort-option\"\n [class.active]=\"isSortActive(sortOption.direction)\"\n (click)=\"onSortOptionClick(sortOption.direction)\"\n >\n <ads-icon\n [name]=\"sortOption.direction === 'asc' ? 'sort_down_to_up' : 'sort_up_to_down'\"\n [theme]=\"isSortActive(sortOption.direction) ? 'secondary' : 'iconPrimary'\"\n size=\"xxs_16\"\n class=\"sort-icon\"\n />\n <span class=\"sort-label\">{{ sortOption.label }}</span>\n @if (isSortActive(sortOption.direction)) {\n <ads-icon\n name=\"check_circle_filled\"\n theme=\"secondary\"\n size=\"xxs_16\"\n />\n }\n </div>\n }\n </div>\n }\n\n @if (hasFilterOptions && hasSortOptions) {\n <ads-divider />\n }\n\n <!-- Filter Section -->\n @if (hasFilterOptions) {\n <div class=\"filter-section\">\n <ads-search-input\n [control]=\"searchControl\"\n placeholder=\"Filter Search\"\n [showFooter]=\"false\"\n />\n\n @if (noResultsFound()) {\n <p class=\"no-results\">\n Can't find \"{{ searchValue() }}.\" Try searching something else.\n </p>\n } @else {\n <div class=\"filter-options\">\n <!-- Select All - only show when not searching -->\n @if (!searchValue()) {\n <ads-checkbox\n [control]=\"selectAllControl\"\n label=\"Select All\"\n [showFooter]=\"false\"\n />\n }\n\n <!-- Filter Options List -->\n @for (option of filteredOptions(); track option.value) {\n <div class=\"filter-option-wrapper\" [matTooltip]=\"option.label\" [matTooltipDisabled]=\"!isTextTruncated(option.label)\">\n <ads-checkbox\n [control]=\"option.control\"\n [label]=\"option.label\"\n [showFooter]=\"false\"\n />\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Hide Column -->\n <div class=\"hide-column-section\" (click)=\"onHideColumnClick()\">\n <ads-icon name=\"visibility_eye_none\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"xxs_16\" />\n <span>Hide Column</span>\n </div>\n</div>\n\n", styles: [".column-sort-filter-menu{width:234px;max-height:358px;background-color:var(--color-white);display:flex;flex-direction:column}.column-sort-filter-menu.full-height{height:358px}.column-sort-filter-menu .sort-section{padding:0;flex-shrink:0}.column-sort-filter-menu .sort-section .sort-option{display:flex;align-items:center;gap:8px;padding:12px;cursor:pointer;transition:background-color .2s ease}.column-sort-filter-menu .sort-section .sort-option .sort-label{font-size:16px;line-height:21px;color:var(--color-dark)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active){background-color:var(--color-secondary-hover)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active) .sort-label{color:var(--color-white)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active) .sort-icon ::ng-deep svg{fill:var(--color-white)!important}.column-sort-filter-menu .sort-section .sort-option.active{background-color:var(--color-secondary-10)}.column-sort-filter-menu .filter-section{padding:0 12px;flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.column-sort-filter-menu .filter-section ads-search-input{display:block;padding:12px 0;flex-shrink:0}.column-sort-filter-menu .filter-section .no-results{color:var(--color-error);font-size:12px;line-height:16px;padding:12px 0;font-weight:600;margin:0}.column-sort-filter-menu .filter-section .filter-options{flex:1;overflow-y:auto;overflow-x:hidden;min-height:0}.column-sort-filter-menu .filter-section .filter-options .filter-option-wrapper ads-checkbox{display:block}.column-sort-filter-menu .filter-section .filter-options .filter-option-wrapper ads-checkbox ::ng-deep .checkbox-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:160px}.column-sort-filter-menu .hide-column-section{display:flex;align-items:center;gap:8px;padding:12px;cursor:pointer;transition:background-color .2s ease;flex-shrink:0;border-top:1px solid var(--color-light)}.column-sort-filter-menu .hide-column-section:hover{background-color:var(--color-secondary-hover)}.column-sort-filter-menu .hide-column-section:hover span{color:var(--color-white)}.column-sort-filter-menu .hide-column-section:hover ads-icon ::ng-deep svg{fill:var(--color-white)!important;stroke:var(--color-white)!important}.column-sort-filter-menu .hide-column-section:active{background-color:var(--color-secondary-pressed)}.column-sort-filter-menu .hide-column-section:active span{color:var(--color-white)}.column-sort-filter-menu .hide-column-section:active ads-icon ::ng-deep svg{fill:var(--color-white)!important;stroke:var(--color-white)!important}.column-sort-filter-menu .hide-column-section span{font-size:16px;line-height:21px;color:var(--color-dark)}\n"], dependencies: [{ kind: "component", type: i1.AdsIconComponent, selector: "ads-icon", inputs: ["size", "name", "color", "theme", "stroke"] }, { kind: "component", type: AdsSearchInputComponent, selector: "ads-search-input", inputs: ["searchCallback", "isIconClickable", "searchEmptyValue", "loading"] }, { kind: "component", type: AdsCheckboxComponent, selector: "ads-checkbox", inputs: ["indeterminate", "width", "tooltip", "tooltipHref", "size"] }, { kind: "component", type: DividerComponent, selector: "ads-divider", inputs: ["margin", "color"] }, { kind: "directive", type: i13.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7270
7276
  }
7271
7277
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AdsColumnSortFilterMenuComponent, decorators: [{
7272
7278
  type: Component,
7273
- args: [{ selector: 'ads-column-sort-filter-menu', standalone: false, template: "<div\n class=\"column-sort-filter-menu\"\n (click)=\"$event.stopPropagation()\"\n [class.full-height]=\"hasFilterOptions && hasSortOptions\"\n>\n <!-- Sort Options -->\n @if (hasSortOptions) {\n <div class=\"sort-section\">\n @for (sortOption of sortOptions; track sortOption.direction) {\n <div\n class=\"sort-option\"\n [class.active]=\"isSortActive(sortOption.direction)\"\n (click)=\"onSortOptionClick(sortOption.direction)\"\n >\n <ads-icon\n [name]=\"sortOption.direction === 'asc' ? 'sort_down_to_up' : 'sort_up_to_down'\"\n [theme]=\"isSortActive(sortOption.direction) ? 'secondary' : 'iconPrimary'\"\n size=\"xxs_16\"\n class=\"sort-icon\"\n />\n <span class=\"sort-label\">{{ sortOption.label }}</span>\n @if (isSortActive(sortOption.direction)) {\n <ads-icon\n name=\"check_circle_filled\"\n theme=\"secondary\"\n size=\"xxs_16\"\n />\n }\n </div>\n }\n </div>\n }\n\n @if (hasFilterOptions && hasSortOptions) {\n <ads-divider />\n }\n\n <!-- Filter Section -->\n @if (hasFilterOptions) {\n <div class=\"filter-section\">\n <ads-search-input\n [control]=\"searchControl\"\n placeholder=\"Filter Search\"\n [showFooter]=\"false\"\n />\n\n @if (noResultsFound()) {\n <p class=\"no-results\">\n Can't find \"{{ searchValue() }}.\" Try searching something else.\n </p>\n } @else {\n <div class=\"filter-options\">\n <!-- Select All - only show when not searching -->\n @if (!searchValue()) {\n <ads-checkbox\n [control]=\"selectAllControl\"\n label=\"Select All\"\n [showFooter]=\"false\"\n />\n }\n\n <!-- Filter Options List -->\n @for (option of filteredOptions(); track option.value) {\n <div class=\"filter-option-wrapper\" [matTooltip]=\"option.label\" [matTooltipDisabled]=\"!isTextTruncated(option.label)\">\n <ads-checkbox\n [control]=\"option.control\"\n [label]=\"option.label\"\n [showFooter]=\"false\"\n />\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Hide Column -->\n <div class=\"hide-column-section\" (click)=\"onHideColumnClick()\">\n <ads-icon name=\"visibility_eye_none\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"xxs_16\" />\n <span>Hide Column</span>\n </div>\n</div>\n\n", styles: [".column-sort-filter-menu{width:234px;max-height:358px;background-color:var(--color-white);display:flex;flex-direction:column}.column-sort-filter-menu.full-height{height:358px}.column-sort-filter-menu .sort-section{padding:0;flex-shrink:0}.column-sort-filter-menu .sort-section .sort-option{display:flex;align-items:center;gap:8px;padding:12px;cursor:pointer;transition:background-color .2s ease}.column-sort-filter-menu .sort-section .sort-option .sort-label{font-size:16px;line-height:21px;color:var(--color-dark)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active){background-color:var(--color-secondary-hover)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active) .sort-label{color:var(--color-white)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active) .sort-icon ::ng-deep svg{fill:var(--color-white)!important}.column-sort-filter-menu .sort-section .sort-option.active{background-color:var(--color-secondary-10)}.column-sort-filter-menu .filter-section{padding:0 12px;flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.column-sort-filter-menu .filter-section ads-search-input{display:block;padding:12px 0;flex-shrink:0}.column-sort-filter-menu .filter-section .no-results{color:var(--color-error);font-size:12px;line-height:16px;padding:12px 0;font-weight:600;margin:0}.column-sort-filter-menu .filter-section .filter-options{flex:1;overflow-y:auto;overflow-x:hidden;min-height:0}.column-sort-filter-menu .filter-section .filter-options .filter-option-wrapper ads-checkbox{display:block}.column-sort-filter-menu .filter-section .filter-options .filter-option-wrapper ads-checkbox ::ng-deep .checkbox-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:160px}.column-sort-filter-menu .hide-column-section{display:flex;align-items:center;gap:8px;padding:12px;cursor:pointer;transition:background-color .2s ease;flex-shrink:0;border-top:1px solid var(--color-light)}.column-sort-filter-menu .hide-column-section:hover{background-color:var(--color-secondary-hover)}.column-sort-filter-menu .hide-column-section:hover span{color:var(--color-white)}.column-sort-filter-menu .hide-column-section:hover ads-icon ::ng-deep svg{fill:var(--color-white)!important;stroke:var(--color-white)!important}.column-sort-filter-menu .hide-column-section:active{background-color:var(--color-secondary-pressed)}.column-sort-filter-menu .hide-column-section:active span{color:var(--color-white)}.column-sort-filter-menu .hide-column-section:active ads-icon ::ng-deep svg{fill:var(--color-white)!important;stroke:var(--color-white)!important}.column-sort-filter-menu .hide-column-section span{font-size:16px;line-height:21px;color:var(--color-dark)}\n"] }]
7279
+ args: [{ selector: 'ads-column-sort-filter-menu', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"column-sort-filter-menu\"\n (click)=\"$event.stopPropagation()\"\n [class.full-height]=\"hasFilterOptions && hasSortOptions\"\n>\n <!-- Sort Options -->\n @if (hasSortOptions) {\n <div class=\"sort-section\">\n @for (sortOption of sortOptions; track sortOption.direction) {\n <div\n class=\"sort-option\"\n [class.active]=\"isSortActive(sortOption.direction)\"\n (click)=\"onSortOptionClick(sortOption.direction)\"\n >\n <ads-icon\n [name]=\"sortOption.direction === 'asc' ? 'sort_down_to_up' : 'sort_up_to_down'\"\n [theme]=\"isSortActive(sortOption.direction) ? 'secondary' : 'iconPrimary'\"\n size=\"xxs_16\"\n class=\"sort-icon\"\n />\n <span class=\"sort-label\">{{ sortOption.label }}</span>\n @if (isSortActive(sortOption.direction)) {\n <ads-icon\n name=\"check_circle_filled\"\n theme=\"secondary\"\n size=\"xxs_16\"\n />\n }\n </div>\n }\n </div>\n }\n\n @if (hasFilterOptions && hasSortOptions) {\n <ads-divider />\n }\n\n <!-- Filter Section -->\n @if (hasFilterOptions) {\n <div class=\"filter-section\">\n <ads-search-input\n [control]=\"searchControl\"\n placeholder=\"Filter Search\"\n [showFooter]=\"false\"\n />\n\n @if (noResultsFound()) {\n <p class=\"no-results\">\n Can't find \"{{ searchValue() }}.\" Try searching something else.\n </p>\n } @else {\n <div class=\"filter-options\">\n <!-- Select All - only show when not searching -->\n @if (!searchValue()) {\n <ads-checkbox\n [control]=\"selectAllControl\"\n label=\"Select All\"\n [showFooter]=\"false\"\n />\n }\n\n <!-- Filter Options List -->\n @for (option of filteredOptions(); track option.value) {\n <div class=\"filter-option-wrapper\" [matTooltip]=\"option.label\" [matTooltipDisabled]=\"!isTextTruncated(option.label)\">\n <ads-checkbox\n [control]=\"option.control\"\n [label]=\"option.label\"\n [showFooter]=\"false\"\n />\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Hide Column -->\n <div class=\"hide-column-section\" (click)=\"onHideColumnClick()\">\n <ads-icon name=\"visibility_eye_none\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"xxs_16\" />\n <span>Hide Column</span>\n </div>\n</div>\n\n", styles: [".column-sort-filter-menu{width:234px;max-height:358px;background-color:var(--color-white);display:flex;flex-direction:column}.column-sort-filter-menu.full-height{height:358px}.column-sort-filter-menu .sort-section{padding:0;flex-shrink:0}.column-sort-filter-menu .sort-section .sort-option{display:flex;align-items:center;gap:8px;padding:12px;cursor:pointer;transition:background-color .2s ease}.column-sort-filter-menu .sort-section .sort-option .sort-label{font-size:16px;line-height:21px;color:var(--color-dark)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active){background-color:var(--color-secondary-hover)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active) .sort-label{color:var(--color-white)}.column-sort-filter-menu .sort-section .sort-option:hover:not(.active) .sort-icon ::ng-deep svg{fill:var(--color-white)!important}.column-sort-filter-menu .sort-section .sort-option.active{background-color:var(--color-secondary-10)}.column-sort-filter-menu .filter-section{padding:0 12px;flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.column-sort-filter-menu .filter-section ads-search-input{display:block;padding:12px 0;flex-shrink:0}.column-sort-filter-menu .filter-section .no-results{color:var(--color-error);font-size:12px;line-height:16px;padding:12px 0;font-weight:600;margin:0}.column-sort-filter-menu .filter-section .filter-options{flex:1;overflow-y:auto;overflow-x:hidden;min-height:0}.column-sort-filter-menu .filter-section .filter-options .filter-option-wrapper ads-checkbox{display:block}.column-sort-filter-menu .filter-section .filter-options .filter-option-wrapper ads-checkbox ::ng-deep .checkbox-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:160px}.column-sort-filter-menu .hide-column-section{display:flex;align-items:center;gap:8px;padding:12px;cursor:pointer;transition:background-color .2s ease;flex-shrink:0;border-top:1px solid var(--color-light)}.column-sort-filter-menu .hide-column-section:hover{background-color:var(--color-secondary-hover)}.column-sort-filter-menu .hide-column-section:hover span{color:var(--color-white)}.column-sort-filter-menu .hide-column-section:hover ads-icon ::ng-deep svg{fill:var(--color-white)!important;stroke:var(--color-white)!important}.column-sort-filter-menu .hide-column-section:active{background-color:var(--color-secondary-pressed)}.column-sort-filter-menu .hide-column-section:active span{color:var(--color-white)}.column-sort-filter-menu .hide-column-section:active ads-icon ::ng-deep svg{fill:var(--color-white)!important;stroke:var(--color-white)!important}.column-sort-filter-menu .hide-column-section span{font-size:16px;line-height:21px;color:var(--color-dark)}\n"] }]
7274
7280
  }], ctorParameters: () => [{ type: i1.AdsIconRegistry }], propDecorators: { config: [{
7275
7281
  type: Input
7276
7282
  }], currentSortDirection: [{
@@ -7345,13 +7351,15 @@ class AdsCustomHeaderComponent {
7345
7351
  }
7346
7352
  closeMenu() {
7347
7353
  this.menuOpen = false;
7354
+ // Notify parent that menu was closed (for pending header refresh)
7355
+ this.params.onMenuClosed?.();
7348
7356
  }
7349
7357
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AdsCustomHeaderComponent, deps: [{ token: i1.AdsIconRegistry }], target: i0.ɵɵFactoryTarget.Component }); }
7350
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: AdsCustomHeaderComponent, isStandalone: false, selector: "ads-custom-header", ngImport: i0, template: "<div class=\"ads-custom-header\">\n <span\n class=\"menu-trigger-anchor\"\n [class.align-right]=\"isLastColumn\"\n #menuTrigger=\"matMenuTrigger\"\n [matMenuTriggerFor]=\"sortFilterMenu\"\n (menuOpened)=\"menuOpen = true\"\n (menuClosed)=\"menuOpen = false\"\n ></span>\n\n <span class=\"header-text\">{{ displayName }}</span>\n\n @if (hasSortFilterConfig) {\n <button\n class=\"header-menu-button\"\n [class.active]=\"isActive\"\n (click)=\"menuTrigger.openMenu(); $event.stopPropagation()\"\n >\n @if (sortDirection === 'asc') {\n <ads-icon name=\"sorting_arrow_up\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n } @else if (sortDirection === 'desc') {\n <ads-icon name=\"sorting_arrow_down\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n } @else {\n <ads-icon name=\"menu_filters\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n }\n </button>\n }\n</div>\n\n<mat-menu #sortFilterMenu=\"matMenu\" class=\"column-sort-filter-panel\" [xPosition]=\"isLastColumn ? 'before' : 'after'\">\n @if (sortFilterConfig) {\n <ads-column-sort-filter-menu\n [config]=\"sortFilterConfig\"\n [currentSortDirection]=\"sortDirection\"\n [selectedFilterValues]=\"filterValues\"\n (sortChanged)=\"onSortChanged($event)\"\n (filterChanged)=\"onFilterChanged($event)\"\n (hideColumn)=\"onHideColumn($event)\"\n />\n }\n</mat-menu>\n\n", styles: [":host{display:block;width:100%;height:100%}.ads-custom-header{display:flex;align-items:center;justify-content:flex-start;width:100%;height:100%;position:relative}.ads-custom-header .menu-trigger-anchor{position:absolute;left:0;bottom:0;width:1px;height:1px;pointer-events:none}.ads-custom-header .menu-trigger-anchor.align-right{left:auto;right:0}.ads-custom-header .header-text{font-weight:600;font-size:16px;line-height:21px;color:var(--color-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ads-custom-header .header-menu-button{display:flex;align-items:center;justify-content:center;gap:2px;padding:4px;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:background-color .2s ease;flex-shrink:0}::ng-deep .column-sort-filter-panel{margin-top:1px}::ng-deep .column-sort-filter-panel .mat-mdc-menu-content{padding:0}\n"], dependencies: [{ kind: "component", type: i1.AdsIconComponent, selector: "ads-icon", inputs: ["size", "name", "color", "theme", "stroke"] }, { kind: "component", type: i4$3.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i4$3.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: AdsColumnSortFilterMenuComponent, selector: "ads-column-sort-filter-menu", inputs: ["config", "currentSortDirection", "selectedFilterValues"], outputs: ["sortChanged", "filterChanged", "hideColumn"] }] }); }
7358
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: AdsCustomHeaderComponent, isStandalone: false, selector: "ads-custom-header", ngImport: i0, template: "<div class=\"ads-custom-header\">\n <span\n class=\"menu-trigger-anchor\"\n [class.align-right]=\"isLastColumn\"\n #menuTrigger=\"matMenuTrigger\"\n [matMenuTriggerFor]=\"sortFilterMenu\"\n (menuOpened)=\"menuOpen = true\"\n (menuClosed)=\"closeMenu()\"\n ></span>\n\n <span class=\"header-text\">{{ displayName }}</span>\n\n @if (hasSortFilterConfig) {\n <button\n class=\"header-menu-button\"\n [class.active]=\"isActive\"\n (click)=\"menuTrigger.openMenu(); $event.stopPropagation()\"\n >\n @if (sortDirection === 'asc') {\n <ads-icon name=\"sorting_arrow_up\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n } @else if (sortDirection === 'desc') {\n <ads-icon name=\"sorting_arrow_down\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n } @else {\n <ads-icon name=\"menu_filters\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n }\n </button>\n }\n</div>\n\n<mat-menu #sortFilterMenu=\"matMenu\" class=\"column-sort-filter-panel\" [xPosition]=\"isLastColumn ? 'before' : 'after'\">\n @if (sortFilterConfig) {\n <ads-column-sort-filter-menu\n [config]=\"sortFilterConfig\"\n [currentSortDirection]=\"sortDirection\"\n [selectedFilterValues]=\"filterValues\"\n (sortChanged)=\"onSortChanged($event)\"\n (filterChanged)=\"onFilterChanged($event)\"\n (hideColumn)=\"onHideColumn($event)\"\n />\n }\n</mat-menu>\n\n", styles: [":host{display:block;width:100%;height:100%}.ads-custom-header{display:flex;align-items:center;justify-content:flex-start;width:100%;height:100%;position:relative}.ads-custom-header .menu-trigger-anchor{position:absolute;left:0;bottom:0;width:1px;height:1px;pointer-events:none}.ads-custom-header .menu-trigger-anchor.align-right{left:auto;right:0}.ads-custom-header .header-text{font-weight:600;font-size:16px;line-height:21px;color:var(--color-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ads-custom-header .header-menu-button{display:flex;align-items:center;justify-content:center;gap:2px;padding:4px;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:background-color .2s ease;flex-shrink:0}::ng-deep .column-sort-filter-panel{margin-top:1px}::ng-deep .column-sort-filter-panel .mat-mdc-menu-content{padding:0}\n"], dependencies: [{ kind: "component", type: i1.AdsIconComponent, selector: "ads-icon", inputs: ["size", "name", "color", "theme", "stroke"] }, { kind: "component", type: i4$3.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i4$3.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: AdsColumnSortFilterMenuComponent, selector: "ads-column-sort-filter-menu", inputs: ["config", "currentSortDirection", "selectedFilterValues"], outputs: ["sortChanged", "filterChanged", "hideColumn"] }] }); }
7351
7359
  }
7352
7360
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AdsCustomHeaderComponent, decorators: [{
7353
7361
  type: Component,
7354
- args: [{ selector: 'ads-custom-header', standalone: false, template: "<div class=\"ads-custom-header\">\n <span\n class=\"menu-trigger-anchor\"\n [class.align-right]=\"isLastColumn\"\n #menuTrigger=\"matMenuTrigger\"\n [matMenuTriggerFor]=\"sortFilterMenu\"\n (menuOpened)=\"menuOpen = true\"\n (menuClosed)=\"menuOpen = false\"\n ></span>\n\n <span class=\"header-text\">{{ displayName }}</span>\n\n @if (hasSortFilterConfig) {\n <button\n class=\"header-menu-button\"\n [class.active]=\"isActive\"\n (click)=\"menuTrigger.openMenu(); $event.stopPropagation()\"\n >\n @if (sortDirection === 'asc') {\n <ads-icon name=\"sorting_arrow_up\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n } @else if (sortDirection === 'desc') {\n <ads-icon name=\"sorting_arrow_down\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n } @else {\n <ads-icon name=\"menu_filters\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n }\n </button>\n }\n</div>\n\n<mat-menu #sortFilterMenu=\"matMenu\" class=\"column-sort-filter-panel\" [xPosition]=\"isLastColumn ? 'before' : 'after'\">\n @if (sortFilterConfig) {\n <ads-column-sort-filter-menu\n [config]=\"sortFilterConfig\"\n [currentSortDirection]=\"sortDirection\"\n [selectedFilterValues]=\"filterValues\"\n (sortChanged)=\"onSortChanged($event)\"\n (filterChanged)=\"onFilterChanged($event)\"\n (hideColumn)=\"onHideColumn($event)\"\n />\n }\n</mat-menu>\n\n", styles: [":host{display:block;width:100%;height:100%}.ads-custom-header{display:flex;align-items:center;justify-content:flex-start;width:100%;height:100%;position:relative}.ads-custom-header .menu-trigger-anchor{position:absolute;left:0;bottom:0;width:1px;height:1px;pointer-events:none}.ads-custom-header .menu-trigger-anchor.align-right{left:auto;right:0}.ads-custom-header .header-text{font-weight:600;font-size:16px;line-height:21px;color:var(--color-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ads-custom-header .header-menu-button{display:flex;align-items:center;justify-content:center;gap:2px;padding:4px;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:background-color .2s ease;flex-shrink:0}::ng-deep .column-sort-filter-panel{margin-top:1px}::ng-deep .column-sort-filter-panel .mat-mdc-menu-content{padding:0}\n"] }]
7362
+ args: [{ selector: 'ads-custom-header', standalone: false, template: "<div class=\"ads-custom-header\">\n <span\n class=\"menu-trigger-anchor\"\n [class.align-right]=\"isLastColumn\"\n #menuTrigger=\"matMenuTrigger\"\n [matMenuTriggerFor]=\"sortFilterMenu\"\n (menuOpened)=\"menuOpen = true\"\n (menuClosed)=\"closeMenu()\"\n ></span>\n\n <span class=\"header-text\">{{ displayName }}</span>\n\n @if (hasSortFilterConfig) {\n <button\n class=\"header-menu-button\"\n [class.active]=\"isActive\"\n (click)=\"menuTrigger.openMenu(); $event.stopPropagation()\"\n >\n @if (sortDirection === 'asc') {\n <ads-icon name=\"sorting_arrow_up\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n } @else if (sortDirection === 'desc') {\n <ads-icon name=\"sorting_arrow_down\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n } @else {\n <ads-icon name=\"menu_filters\" theme=\"iconPrimary\" stroke=\"iconPrimary\" size=\"auto\" />\n }\n </button>\n }\n</div>\n\n<mat-menu #sortFilterMenu=\"matMenu\" class=\"column-sort-filter-panel\" [xPosition]=\"isLastColumn ? 'before' : 'after'\">\n @if (sortFilterConfig) {\n <ads-column-sort-filter-menu\n [config]=\"sortFilterConfig\"\n [currentSortDirection]=\"sortDirection\"\n [selectedFilterValues]=\"filterValues\"\n (sortChanged)=\"onSortChanged($event)\"\n (filterChanged)=\"onFilterChanged($event)\"\n (hideColumn)=\"onHideColumn($event)\"\n />\n }\n</mat-menu>\n\n", styles: [":host{display:block;width:100%;height:100%}.ads-custom-header{display:flex;align-items:center;justify-content:flex-start;width:100%;height:100%;position:relative}.ads-custom-header .menu-trigger-anchor{position:absolute;left:0;bottom:0;width:1px;height:1px;pointer-events:none}.ads-custom-header .menu-trigger-anchor.align-right{left:auto;right:0}.ads-custom-header .header-text{font-weight:600;font-size:16px;line-height:21px;color:var(--color-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ads-custom-header .header-menu-button{display:flex;align-items:center;justify-content:center;gap:2px;padding:4px;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:background-color .2s ease;flex-shrink:0}::ng-deep .column-sort-filter-panel{margin-top:1px}::ng-deep .column-sort-filter-panel .mat-mdc-menu-content{padding:0}\n"] }]
7355
7363
  }], ctorParameters: () => [{ type: i1.AdsIconRegistry }] });
7356
7364
 
7357
7365
  class AdsColumnSortFilterMenuModule {
@@ -7501,20 +7509,43 @@ class AdsTableComponent {
7501
7509
  adsCustomHeader: AdsCustomHeaderComponent,
7502
7510
  };
7503
7511
  /** @ignore */
7504
- this.filteredColumns = [];
7512
+ this.filteredColumns = signal([], ...(ngDevMode ? [{ debugName: "filteredColumns" }] : []));
7513
+ /** @ignore - Flag indicating headers need refresh when menu closes (for dynamic filter options) */
7514
+ this.pendingHeaderRefresh = false;
7505
7515
  /** @ignore */
7506
7516
  this.columnVisibilityMenuOpen = false;
7517
+ /** @ignore - Cache for filtered column values to avoid recalculating on every access */
7518
+ this.filteredColumnValuesCache = new Map();
7519
+ /** @ignore - Cache version to invalidate cache when filters change */
7520
+ this.filterCacheVersion = 0;
7521
+ /** @ignore - Cache for unique column values (all values, not filtered) */
7522
+ this.uniqueColumnValuesCache = new Map();
7523
+ /** @ignore - Data version to invalidate unique values cache when rowData changes */
7524
+ this.dataVersion = 0;
7507
7525
  /** @ignore */
7508
7526
  this.isExternalFilterPresent = () => {
7509
7527
  // Check if any custom column filter is active
7510
7528
  for (const [field, values] of this.columnFilterStates()) {
7511
- const config = this.getColumnSortFilterConfig(field);
7512
- if (config?.filterOptions) {
7513
- // Filter is active if some but not all options are selected, OR if no options are selected
7514
- if (values.length === 0 || (values.length > 0 && values.length < config.filterOptions.length)) {
7529
+ // If no values selected, filter is active (show nothing)
7530
+ if (values.length === 0) {
7531
+ return true;
7532
+ }
7533
+ // Check if this column has explicit config in columnSortFilterConfigs
7534
+ const explicitConfig = this.columnSortFilterConfigs.find(c => c.field === field);
7535
+ if (explicitConfig) {
7536
+ // For columns with explicit config, check if not all options are selected
7537
+ // Use the original filterOptions from config if provided, otherwise get unique values
7538
+ const baseFilterOptions = explicitConfig.filterOptions && explicitConfig.filterOptions.length > 0
7539
+ ? explicitConfig.filterOptions
7540
+ : this.getUniqueColumnValues(field);
7541
+ if (baseFilterOptions.length > 0 && values.length < baseFilterOptions.length) {
7515
7542
  return true;
7516
7543
  }
7517
7544
  }
7545
+ else {
7546
+ // For columns without explicit config, any stored filter state means filter is active
7547
+ return true;
7548
+ }
7518
7549
  }
7519
7550
  return false;
7520
7551
  };
@@ -7523,17 +7554,25 @@ class AdsTableComponent {
7523
7554
  if (!node.data)
7524
7555
  return true;
7525
7556
  for (const [field, selectedValues] of this.columnFilterStates()) {
7526
- const config = this.getColumnSortFilterConfig(field);
7527
- if (!config?.filterOptions)
7528
- continue;
7529
7557
  // If no values are selected, hide all rows for this filter
7530
7558
  if (selectedValues.length === 0) {
7531
7559
  return false;
7532
7560
  }
7533
- // If all values are selected, don't filter (show all)
7534
- if (selectedValues.length === config.filterOptions.length) {
7535
- continue;
7561
+ // Check if this column has explicit config in columnSortFilterConfigs
7562
+ const explicitConfig = this.columnSortFilterConfigs.find(c => c.field === field);
7563
+ if (explicitConfig) {
7564
+ // For columns with explicit config, check if all options are selected
7565
+ // Use the original filterOptions from config if provided, otherwise get unique values
7566
+ const baseFilterOptions = explicitConfig.filterOptions && explicitConfig.filterOptions.length > 0
7567
+ ? explicitConfig.filterOptions
7568
+ : this.getUniqueColumnValues(field);
7569
+ if (baseFilterOptions.length > 0 && selectedValues.length === baseFilterOptions.length) {
7570
+ // All values selected = no filter, skip this column
7571
+ continue;
7572
+ }
7536
7573
  }
7574
+ // For columns without explicit config (like supplier, destination),
7575
+ // always apply the filter based on selectedValues
7537
7576
  const cellValue = node.data[field];
7538
7577
  const cellValueStr = cellValue != null ? String(cellValue) : '';
7539
7578
  if (!selectedValues.includes(cellValueStr)) {
@@ -7589,6 +7628,7 @@ class AdsTableComponent {
7589
7628
  onSortChanged: this.onColumnSortChanged.bind(this),
7590
7629
  onFilterChanged: this.onColumnFilterChanged.bind(this),
7591
7630
  onHideColumn: this.onHideColumn.bind(this),
7631
+ onMenuClosed: this.onColumnMenuClosed.bind(this),
7592
7632
  getSortDirection: this.getColumnSortDirection.bind(this),
7593
7633
  getFilterValues: this.getColumnFilterValues.bind(this),
7594
7634
  isColumnSorted: this.isColumnSorted.bind(this),
@@ -7636,12 +7676,16 @@ class AdsTableComponent {
7636
7676
  ngOnChanges(changes) {
7637
7677
  // When rowData changes (e.g., async load), refresh the column definitions
7638
7678
  // so that filterOptions are recalculated from the new data
7639
- if (changes['rowData'] && !changes['rowData'].firstChange && this.enableCustomSortFilter) {
7679
+ if (changes['rowData'] && this.enableCustomSortFilter) {
7640
7680
  const newData = changes['rowData'].currentValue;
7641
7681
  const oldData = changes['rowData'].previousValue;
7642
- // Only refresh if we went from empty to having data, or if data changed significantly
7682
+ // Invalidate all caches when rowData changes
7683
+ this.invalidateDataCache();
7684
+ // Refresh headers if we went from empty/null to having data
7643
7685
  if ((!oldData || oldData.length === 0) && newData && newData.length > 0) {
7644
- this.refreshColumnHeaders();
7686
+ Promise.resolve().then(() => {
7687
+ this.refreshColumnHeaders();
7688
+ });
7645
7689
  }
7646
7690
  }
7647
7691
  }
@@ -7783,16 +7827,17 @@ class AdsTableComponent {
7783
7827
  }
7784
7828
  /** @ignore */
7785
7829
  get filterButtonLabel() {
7786
- if (this.filteredColumns.length === 0) {
7830
+ const filtered = this.filteredColumns();
7831
+ if (filtered.length === 0) {
7787
7832
  return 'Filter';
7788
7833
  }
7789
- else if (this.filteredColumns.length === 1) {
7790
- const column = this.getActiveColumnDefs().find((col) => col.field === this.filteredColumns[0]);
7791
- const columnName = column?.headerName || column?.field || this.filteredColumns[0];
7834
+ else if (filtered.length === 1) {
7835
+ const column = this.getActiveColumnDefs().find((col) => col.field === filtered[0]);
7836
+ const columnName = column?.headerName || column?.field || filtered[0];
7792
7837
  return `Filtered by ${columnName}`;
7793
7838
  }
7794
7839
  else {
7795
- const columnNames = this.filteredColumns.map((fieldName) => {
7840
+ const columnNames = filtered.map((fieldName) => {
7796
7841
  const column = this.getActiveColumnDefs().find((col) => col.field === fieldName);
7797
7842
  return column?.headerName || column?.field || fieldName;
7798
7843
  });
@@ -7805,7 +7850,7 @@ class AdsTableComponent {
7805
7850
  }
7806
7851
  /** @ignore */
7807
7852
  get isFilteredTable() {
7808
- return this.filteredColumns.length > 0;
7853
+ return this.filteredColumns().length > 0;
7809
7854
  }
7810
7855
  /** @ignore */
7811
7856
  updateFilteringState() {
@@ -7817,28 +7862,30 @@ class AdsTableComponent {
7817
7862
  }
7818
7863
  // Check custom column filter states
7819
7864
  for (const [field, values] of this.columnFilterStates()) {
7820
- const config = this.getColumnSortFilterConfig(field);
7821
- if (config?.filterOptions) {
7865
+ // Check if this column has explicit config in columnSortFilterConfigs
7866
+ const explicitConfig = this.columnSortFilterConfigs.find(c => c.field === field);
7867
+ if (explicitConfig) {
7868
+ // Use the original filterOptions from config if provided, otherwise get unique values
7869
+ const baseFilterOptions = explicitConfig.filterOptions && explicitConfig.filterOptions.length > 0
7870
+ ? explicitConfig.filterOptions
7871
+ : this.getUniqueColumnValues(field);
7822
7872
  // Column is filtered if:
7823
7873
  // - No options are selected (empty array = all unchecked)
7824
7874
  // - Some but not all options are selected
7825
- // - filterOptions is empty but we have stored values (data not loaded yet)
7826
- const hasFilterOptions = config.filterOptions.length > 0;
7827
7875
  const isFiltered = values.length === 0 ||
7828
- (hasFilterOptions && values.length < config.filterOptions.length) ||
7829
- (!hasFilterOptions && values.length > 0);
7876
+ (baseFilterOptions.length > 0 && values.length < baseFilterOptions.length);
7830
7877
  if (isFiltered && !filteredColumnsList.includes(field)) {
7831
7878
  filteredColumnsList.push(field);
7832
7879
  }
7833
7880
  }
7834
7881
  else if (values.length > 0) {
7835
- // If no config but we have filter values, consider it filtered
7882
+ // For columns without explicit config, any stored filter state means filter is active
7836
7883
  if (!filteredColumnsList.includes(field)) {
7837
7884
  filteredColumnsList.push(field);
7838
7885
  }
7839
7886
  }
7840
7887
  }
7841
- this.filteredColumns = filteredColumnsList;
7888
+ this.filteredColumns.set(filteredColumnsList);
7842
7889
  }
7843
7890
  /** @ignore */
7844
7891
  onSortChanged() {
@@ -7866,16 +7913,23 @@ class AdsTableComponent {
7866
7913
  // Clear custom column filter states (reset to all options selected)
7867
7914
  const newStates = new Map();
7868
7915
  for (const [field] of this.columnFilterStates()) {
7869
- const config = this.getColumnSortFilterConfig(field);
7916
+ // Use base config to get ALL options, not filtered ones
7917
+ const config = this.getBaseColumnSortFilterConfig(field);
7870
7918
  if (config?.filterOptions) {
7871
7919
  // Reset to all options selected
7872
7920
  newStates.set(field, [...config.filterOptions]);
7873
7921
  }
7874
7922
  }
7875
7923
  this.columnFilterStates.set(newStates);
7924
+ // Invalidate the filter cache since filters changed
7925
+ this.invalidateFilterCache();
7876
7926
  // Trigger external filter update
7877
7927
  this.gridApi.onFilterChanged();
7878
7928
  this.updateFilteringState();
7929
+ // Refresh headers to update filter options in menus
7930
+ if (this.enableCustomSortFilter) {
7931
+ this.gridApi.refreshHeader();
7932
+ }
7879
7933
  // Emit event to notify the header template that filters were cleared
7880
7934
  this.filtersCleared.emit();
7881
7935
  }
@@ -7892,11 +7946,17 @@ class AdsTableComponent {
7892
7946
  }
7893
7947
  }
7894
7948
  // ============ Custom Sort/Filter Menu Methods ============
7895
- /** @ignore - Extract unique values from a column in rowData */
7949
+ /** @ignore - Extract unique values from a column in rowData (with caching) */
7896
7950
  getUniqueColumnValues(field) {
7897
7951
  if (!this.rowData || this.rowData.length === 0) {
7898
7952
  return [];
7899
7953
  }
7954
+ // Check cache first
7955
+ const cacheKey = `${field}_v${this.dataVersion}`;
7956
+ const cached = this.uniqueColumnValuesCache.get(cacheKey);
7957
+ if (cached) {
7958
+ return cached;
7959
+ }
7900
7960
  const uniqueValues = new Set();
7901
7961
  this.rowData.forEach(row => {
7902
7962
  const value = row[field];
@@ -7904,14 +7964,145 @@ class AdsTableComponent {
7904
7964
  uniqueValues.add(String(value));
7905
7965
  }
7906
7966
  });
7907
- return Array.from(uniqueValues).sort();
7967
+ const result = Array.from(uniqueValues).sort();
7968
+ // Cache the result
7969
+ this.uniqueColumnValuesCache.set(cacheKey, result);
7970
+ return result;
7971
+ }
7972
+ /**
7973
+ * @ignore - Extract unique values for a column, considering filters from OTHER columns only.
7974
+ * This enables cross-filtering: when you filter Status="Delayed", the Destination filter
7975
+ * shows all destinations that have at least one "Delayed" row, and vice versa.
7976
+ * Uses caching to avoid expensive recalculations on every access.
7977
+ */
7978
+ getFilteredColumnValues(field) {
7979
+ if (!this.rowData || this.rowData.length === 0) {
7980
+ return [];
7981
+ }
7982
+ // Check cache first
7983
+ const cacheKey = `${field}_v${this.filterCacheVersion}`;
7984
+ const cached = this.filteredColumnValuesCache.get(cacheKey);
7985
+ if (cached) {
7986
+ return cached;
7987
+ }
7988
+ // Check if any filter is active (excluding the current field)
7989
+ const hasOtherActiveFilters = this.hasActiveFiltersExcluding(field);
7990
+ // If no other filters are active, return all unique values
7991
+ if (!hasOtherActiveFilters) {
7992
+ const values = this.getUniqueColumnValues(field);
7993
+ this.filteredColumnValuesCache.set(cacheKey, values);
7994
+ return values;
7995
+ }
7996
+ const uniqueValues = new Set();
7997
+ // Iterate through all rows and check if they pass filters from OTHER columns
7998
+ this.rowData.forEach(row => {
7999
+ if (this.doesRowPassFiltersExcluding(row, field)) {
8000
+ const value = row[field];
8001
+ if (value !== null && value !== undefined) {
8002
+ uniqueValues.add(String(value));
8003
+ }
8004
+ }
8005
+ });
8006
+ // If no values found, fall back to all values
8007
+ let result;
8008
+ if (uniqueValues.size === 0) {
8009
+ result = this.getUniqueColumnValues(field);
8010
+ }
8011
+ else {
8012
+ result = Array.from(uniqueValues).sort();
8013
+ }
8014
+ // Cache the result
8015
+ this.filteredColumnValuesCache.set(cacheKey, result);
8016
+ return result;
8017
+ }
8018
+ /** @ignore - Invalidate the filtered column values cache */
8019
+ invalidateFilterCache() {
8020
+ this.filterCacheVersion++;
8021
+ this.filteredColumnValuesCache.clear();
8022
+ }
8023
+ /** @ignore - Invalidate the unique column values cache (when rowData changes) */
8024
+ invalidateDataCache() {
8025
+ this.dataVersion++;
8026
+ this.uniqueColumnValuesCache.clear();
8027
+ // Also invalidate filter cache since it depends on data
8028
+ this.invalidateFilterCache();
8029
+ }
8030
+ /** @ignore - Check if there are any active filters excluding the specified field */
8031
+ hasActiveFiltersExcluding(excludeField) {
8032
+ for (const [field, selectedValues] of this.columnFilterStates()) {
8033
+ if (field === excludeField)
8034
+ continue;
8035
+ const config = this.getBaseColumnSortFilterConfig(field);
8036
+ if (!config?.filterOptions)
8037
+ continue;
8038
+ // Filter is active if not all options are selected
8039
+ if (selectedValues.length < config.filterOptions.length) {
8040
+ return true;
8041
+ }
8042
+ }
8043
+ return false;
8044
+ }
8045
+ /** @ignore - Check if a row passes all filters EXCEPT the specified field */
8046
+ doesRowPassFiltersExcluding(row, excludeField) {
8047
+ for (const [field, selectedValues] of this.columnFilterStates()) {
8048
+ if (field === excludeField)
8049
+ continue;
8050
+ const config = this.getBaseColumnSortFilterConfig(field);
8051
+ if (!config?.filterOptions)
8052
+ continue;
8053
+ // If all values are selected, this filter doesn't restrict anything
8054
+ if (selectedValues.length === config.filterOptions.length) {
8055
+ continue;
8056
+ }
8057
+ // If no values are selected, no rows pass
8058
+ if (selectedValues.length === 0) {
8059
+ return false;
8060
+ }
8061
+ const cellValue = row[field];
8062
+ const cellValueStr = cellValue != null ? String(cellValue) : '';
8063
+ if (!selectedValues.includes(cellValueStr)) {
8064
+ return false;
8065
+ }
8066
+ }
8067
+ return true;
8068
+ }
8069
+ /** @ignore - Get base config without dynamic filter options */
8070
+ getBaseColumnSortFilterConfig(field) {
8071
+ const config = this.columnSortFilterConfigs.find(c => c.field === field);
8072
+ // If column is not in columnSortFilterConfigs but has filter state, create a minimal config
8073
+ if (!config) {
8074
+ // Check if this field has filter state (was filtered at some point)
8075
+ if (this.columnFilterStates().has(field)) {
8076
+ return {
8077
+ field,
8078
+ headerName: field,
8079
+ filterOptions: this.getUniqueColumnValues(field),
8080
+ };
8081
+ }
8082
+ return undefined;
8083
+ }
8084
+ // Auto-populate filterOptions from all rowData if not provided
8085
+ if (!config.filterOptions || config.filterOptions.length === 0) {
8086
+ return {
8087
+ ...config,
8088
+ filterOptions: this.getUniqueColumnValues(field),
8089
+ };
8090
+ }
8091
+ return config;
7908
8092
  }
7909
8093
  /** @ignore */
7910
8094
  getColumnSortFilterConfig(field) {
7911
- const config = this.columnSortFilterConfigs.find(config => config.field === field);
8095
+ const config = this.columnSortFilterConfigs.find(c => c.field === field);
7912
8096
  if (!config) {
7913
8097
  return undefined;
7914
8098
  }
8099
+ // When custom sort/filter is enabled, get values from filtered rows (dynamic filtering)
8100
+ if (this.enableCustomSortFilter && this.gridApi) {
8101
+ return {
8102
+ ...config,
8103
+ filterOptions: this.getFilteredColumnValues(field),
8104
+ };
8105
+ }
7915
8106
  // Auto-populate filterOptions from rowData if not provided
7916
8107
  if (!config.filterOptions || config.filterOptions.length === 0) {
7917
8108
  return {
@@ -7932,7 +8123,8 @@ class AdsTableComponent {
7932
8123
  return storedValues;
7933
8124
  }
7934
8125
  // If no filter state exists for this field, return all options (initial state = all selected)
7935
- const config = this.getColumnSortFilterConfig(field);
8126
+ // Always use base config here to get ALL possible options, not filtered ones
8127
+ const config = this.getBaseColumnSortFilterConfig(field);
7936
8128
  return config?.filterOptions ?? [];
7937
8129
  }
7938
8130
  /** @ignore */
@@ -7941,7 +8133,8 @@ class AdsTableComponent {
7941
8133
  }
7942
8134
  /** @ignore */
7943
8135
  isColumnFiltered(field) {
7944
- const config = this.getColumnSortFilterConfig(field);
8136
+ // Use base config to get all possible options (not filtered options)
8137
+ const config = this.getBaseColumnSortFilterConfig(field);
7945
8138
  if (!config?.filterOptions)
7946
8139
  return false;
7947
8140
  const selectedValues = this.columnFilterStates().get(field);
@@ -7998,14 +8191,29 @@ class AdsTableComponent {
7998
8191
  const newStates = new Map(this.columnFilterStates());
7999
8192
  newStates.set(event.field, event.values);
8000
8193
  this.columnFilterStates.set(newStates);
8194
+ // Invalidate the filter cache since filters changed
8195
+ this.invalidateFilterCache();
8001
8196
  // Apply external filter to grid immediately
8002
8197
  if (this.gridApi) {
8003
8198
  this.gridApi.onFilterChanged();
8004
8199
  this.updateFilteringState();
8200
+ // When custom sort/filter is enabled, mark that headers need refresh
8201
+ // The actual refresh will happen when the menu closes to avoid destroying the open menu
8202
+ if (this.enableCustomSortFilter) {
8203
+ this.pendingHeaderRefresh = true;
8204
+ }
8005
8205
  }
8006
8206
  // Emit event for external listeners
8007
8207
  this.columnFilterChanged.emit(event);
8008
8208
  }
8209
+ /** @ignore - Called when a column's sort/filter menu is closed */
8210
+ onColumnMenuClosed() {
8211
+ // If custom sort/filter is enabled and there's a pending refresh, do it now
8212
+ if (this.enableCustomSortFilter && this.pendingHeaderRefresh && this.gridApi) {
8213
+ this.pendingHeaderRefresh = false;
8214
+ this.gridApi.refreshHeader();
8215
+ }
8216
+ }
8009
8217
  /** @ignore */
8010
8218
  onHideColumn(field) {
8011
8219
  const visibilityItem = this.columnVisibilityList().find(item => item.field === field);
@@ -8109,11 +8317,7 @@ class AdsTableComponent {
8109
8317
  params.api.sizeColumnsToFit();
8110
8318
  // Re-evaluate filtering state now that data is available
8111
8319
  // This ensures filter button shows correct state when filters were applied before data loaded
8112
- // Use setTimeout to avoid ExpressionChangedAfterItHasBeenCheckedError
8113
- setTimeout(() => {
8114
- this.updateFilteringState();
8115
- this.cdr.detectChanges();
8116
- });
8320
+ this.updateFilteringState();
8117
8321
  }
8118
8322
  /** @ignore */
8119
8323
  getAgGridIcons() {
@@ -8291,12 +8495,32 @@ class AdsTableComponent {
8291
8495
  ? new Map(filterStates)
8292
8496
  : new Map(Object.entries(filterStates));
8293
8497
  this.columnFilterStates.set(newStates);
8294
- // Apply external filter to grid
8295
- if (this.gridApi) {
8296
- this.gridApi.onFilterChanged();
8297
- this.updateFilteringState();
8498
+ }
8499
+ /**
8500
+ * Applies the current column filter states to the grid.
8501
+ * Call this method after setting filter states and when data is available to sync filters with grid data.
8502
+ */
8503
+ applyFiltersToGrid() {
8504
+ if (!this.gridApi) {
8505
+ return;
8506
+ }
8507
+ // Force AG Grid to re-evaluate external filters
8508
+ this.gridApi.onFilterChanged();
8509
+ // Update filtering state for UI (filter button, etc.)
8510
+ this.updateFilteringState();
8511
+ // Refresh headers to update filter button states and menu checkboxes
8512
+ if (this.enableCustomSortFilter) {
8513
+ this.gridApi.refreshHeader();
8298
8514
  }
8299
8515
  }
8516
+ /**
8517
+ * Reapplies the current column filter states to the grid.
8518
+ * Use this method when data is loaded asynchronously and filters need to be reapplied.
8519
+ * @deprecated Use applyFiltersToGrid() instead
8520
+ */
8521
+ applyColumnFilters() {
8522
+ this.applyFiltersToGrid();
8523
+ }
8300
8524
  /** @ignore */
8301
8525
  ngOnDestroy() {
8302
8526
  // Clean up the observer when the component is destroyed